},
show : function(backward) {
- let appMonitor = Shell.AppMonitor.get_default();
- let apps = appMonitor.get_running_apps ("");
+ let tracker = Shell.WindowTracker.get_default();
+ let apps = tracker.get_running_apps ("");
if (!apps.length)
return false;
const Gettext = imports.gettext.domain('gnome-shell');
const _ = Gettext.gettext;
+const AppFavorites = imports.ui.appFavorites;
const AppIcon = imports.ui.appIcon;
const DND = imports.ui.dnd;
const GenericDisplay = imports.ui.genericDisplay;
// Opens an application represented by this display item.
launch : function() {
- let windows = Shell.AppMonitor.get_default().get_windows_for_app(this._appInfo.get_id());
+ let appSys = Shell.AppSystem.get_default();
+ let app = appSys.get_app(this._appInfo.get_id());
+ let windows = app.get_windows();
if (windows.length > 0) {
let mostRecentWindow = windows[0];
Main.overview.activateWindow(mostRecentWindow, Main.currentTime());
// We use a map of appIds instead of an array to ensure that we don't have duplicates and for easier lookup.
this._menuSearchAppMatches = {};
- this._appMonitor = Shell.AppMonitor.get_default();
this._appSystem = Shell.AppSystem.get_default();
this._appsStale = true;
this._appSystem.connect('installed-changed', Lang.bind(this, function(appSys) {
this._appsStale = true;
this._redisplay(GenericDisplay.RedisplayFlags.NONE);
}));
- this._appSystem.connect('favorites-changed', Lang.bind(this, function(appSys) {
- this._appsStale = true;
- this._redisplay(GenericDisplay.RedisplayFlags.NONE);
- }));
this._focusInMenus = true;
this._activeMenuIndex = -1;
_getMostUsed: function() {
let context = "";
- return this._appMonitor.get_most_used_apps(context, 30).map(Lang.bind(this, function (id) {
- return this._appSystem.lookup_cached_app(id);
- })).filter(function (e) { return e != null });
+ let usage = Shell.AppUsage.get_default();
+ return usage.get_most_used(context, 30);
},
_addMenuItem: function(name, id, index) {
// as say Pidgin, but ideally what we do there is have the app
// express to us that it doesn't do relaunch=new-window in the
// .desktop file.
- this.app.get_info().launch();
+ this.app.launch();
},
getDragActor: function() {
let modifiers = Shell.get_event_state(event);
if (modifiers & Clutter.ModifierType.CONTROL_MASK) {
- this.app.get_info().launch();
+ this.app.launch();
} else {
this.activateMostRecentWindow();
}
},
_onActivate: function() {
- this.app.get_info().launch();
+ this.app.launch();
Main.overview.hide();
return true;
},
this._menus = [];
this._menuDisplays = [];
+ this._favorites = [];
+
this.actor = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
x_align: Big.BoxAlignment.CENTER });
this.actor._delegate = this;
this._grid = new WellGrid();
this.actor.append(this._grid.actor, Big.BoxPackFlags.EXPAND);
+ this._tracker = Shell.WindowTracker.get_default();
this._appSystem = Shell.AppSystem.get_default();
- this._appMonitor = Shell.AppMonitor.get_default();
- this._appSystem.connect('installed-changed', Lang.bind(this, function(appSys) {
- this._redisplay();
- }));
- this._appSystem.connect('favorites-changed', Lang.bind(this, function(appSys) {
- this._redisplay();
- }));
- this._appMonitor.connect('window-added', Lang.bind(this, function(monitor) {
- this._redisplay();
- }));
- this._appMonitor.connect('window-removed', Lang.bind(this, function(monitor) {
- this._redisplay();
- }));
+ this._appSystem.connect('installed-changed', Lang.bind(this, this._redisplay));
+
+ AppFavorites.getAppFavorites().connect('changed', Lang.bind(this, this._redisplay));
+ this._tracker.connect('app-running-changed', Lang.bind(this, this._redisplay));
this._redisplay();
},
this._grid.removeAll();
- let favorites = this._appMonitor.get_favorites();
- let favoriteIds = this._appIdListToHash(favorites);
+ let favorites = AppFavorites.getAppFavorites().getFavoriteMap();
/* hardcode here pending some design about how exactly desktop contexts behave */
let contextId = "";
- let running = this._appMonitor.get_running_apps(contextId);
+ let running = this._tracker.get_running_apps(contextId);
let runningIds = this._appIdListToHash(running);
- for (let i = 0; i < favorites.length; i++) {
- let app = favorites[i];
+ for (let id in favorites) {
+ let app = favorites[id];
let display;
if (app.get_windows().length > 0) {
display = new RunningWellItem(app, true);
for (let i = 0; i < running.length; i++) {
let app = running[i];
- if (app.get_id() in favoriteIds)
+ if (app.get_id() in favorites)
continue;
let display = new RunningWellItem(app, false);
this._grid.actor.add_actor(display.actor);
// Draggable target interface
acceptDrop : function(source, actor, x, y, time) {
- let appSystem = Shell.AppSystem.get_default();
-
let app = null;
if (source instanceof AppDisplayItem) {
- app = appSystem.lookup_cached_app(source.getId());
+ app = this._appSystem.get_app(source.getId());
} else if (source instanceof Workspaces.WindowClone) {
- let appMonitor = Shell.AppMonitor.get_default();
- app = appMonitor.get_window_app(source.metaWindow);
+ app = this._tracker.get_window_app(source.metaWindow);
}
// Don't allow favoriting of transient apps
let id = app.get_id();
- let favorites = this._appMonitor.get_favorites();
- let favoriteIds = this._appIdListToHash(favorites);
+ let favorites = AppFavorites.getAppFavorites().getFavoriteMap();
- let srcIsFavorite = (id in favoriteIds);
+ let srcIsFavorite = (id in favorites);
if (srcIsFavorite) {
return false;
} else {
- Mainloop.idle_add(function () {
- appSystem.add_favorite(id);
+ Mainloop.idle_add(Lang.bind(this, function () {
+ AppFavorites.getAppFavorites().addFavorite(id);
return false;
- });
+ }));
}
return true;
--- /dev/null
+/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+const Shell = imports.gi.Shell;
+const Lang = imports.lang;
+const Signals = imports.signals;
+
+
+function AppFavorites() {
+ this._init();
+}
+
+AppFavorites.prototype = {
+ FAVORITE_APPS_KEY: 'favorite_apps',
+
+ _init: function() {
+ this._favorites = {};
+ this._gconf = Shell.GConf.get_default();
+ this._gconf.connect('changed::' + this.FAVORITE_APPS_KEY, Lang.bind(this, this._onFavsChanged));
+ this._reload();
+ },
+
+ _onFavsChanged: function() {
+ this._reload();
+ this.emit('changed');
+ },
+
+ _reload: function() {
+ let ids = Shell.GConf.get_default().get_string_list('favorite_apps');
+ let appSys = Shell.AppSystem.get_default();
+ let apps = ids.map(function (id) {
+ return appSys.get_app(id);
+ }).filter(function (app) {
+ return app != null;
+ });
+ this._favorites = {};
+ for (let i = 0; i < apps.length; i++) {
+ let app = apps[i];
+ this._favorites[app.get_id()] = app;
+ }
+ },
+
+ _getIds: function() {
+ let ret = [];
+ for (let id in this._favorites)
+ ret.push(id);
+ return ret;
+ },
+
+ getFavoriteMap: function() {
+ return this._favorites;
+ },
+
+ getFavorites: function() {
+ let ret = [];
+ for (let id in this._favorites)
+ ret.push(this._favorites[id]);
+ return ret;
+ },
+
+ isFavorite: function(appId) {
+ return appId in this._favorites;
+ },
+
+ addFavorite: function(appId) {
+ if (appId in this._favorites)
+ return;
+ let app = Shell.AppSystem.get_default().get_app(appId);
+ if (!app)
+ return;
+ let ids = this._getIds();
+ ids.push(appId);
+ this._gconf.set_string_list(this.FAVORITE_APPS_KEY, ids);
+ this._favorites[appId] = app;
+ },
+
+ removeFavorite: function(appId) {
+ if (!appId in this._favorites)
+ return;
+ let ids = this._getIds().filter(function (id) { return id != appId; });
+ this._gconf.set_string_list(this.FAVORITE_APPS_KEY, ids);
+ }
+};
+Signals.addSignalMethods(AppFavorites.prototype);
+
+var appFavoritesInstance = null;
+function getAppFavorites() {
+ if (appFavoritesInstance == null)
+ appFavoritesInstance = new AppFavorites();
+ return appFavoritesInstance;
+}
const _ = Gettext.gettext;
const GenericDisplay = imports.ui.genericDisplay;
+const AppFavorites = imports.ui.appFavorites;
const Main = imports.ui.main;
const Workspaces = imports.ui.workspaces;
if (windows.length > 0)
this._appendSeparator();
+ let isFavorite = AppFavorites.getAppFavorites().isFavorite(this._source.app.get_id());
+
this._newWindowMenuItem = windows.length > 0 ? this._appendMenuItem(null, _("New Window")) : null;
- let favorites = Shell.AppSystem.get_default().get_favorites();
- let id = this._source.app.get_id();
- this._isFavorite = false;
- for (let i = 0; i < favorites.length; i++) {
- if (id == favorites[i]) {
- this._isFavorite = true;
- break;
- }
- }
if (windows.length > 0)
this._appendSeparator();
- this._toggleFavoriteMenuItem = this._appendMenuItem(null, this._isFavorite ? _("Remove from Favorites")
+ this._toggleFavoriteMenuItem = this._appendMenuItem(null, isFavorite ? _("Remove from Favorites")
: _("Add to Favorites"));
this._highlightedItem = null;
let metaWindow = child._window;
this.emit('activate-window', metaWindow);
} else if (child == this._newWindowMenuItem) {
- this._source.app.get_info().launch();
+ this._source.app.launch();
this.emit('activate-window', null);
} else if (child == this._toggleFavoriteMenuItem) {
- let appSys = Shell.AppSystem.get_default();
- if (this._isFavorite)
- appSys.remove_favorite(this._source.app.get_id());
+ let isFavorite = AppFavorites.getAppFavorites().isFavorite(this._source.app.get_id());
+ if (isFavorite)
+ favs.removeFavorite(this._source.app.get_id());
else
- appSys.add_favorite(this._source.app.get_id());
+ favs.addFavorite(this._source.app.get_id());
}
this.popdown();
},
Environment.init();
- // Ensure ShellAppMonitor is initialized; this will
+ // Ensure ShellWindowTracker and ShellAppUsage are initialized; this will
// also initialize ShellAppSystem first. ShellAppSystem
- // needs to load all the .desktop files, and ShellAppMonitor
+ // needs to load all the .desktop files, and ShellWindowTracker
// will use those to associate with windows. Right now
// the Monitor doesn't listen for installed app changes
// and recalculate application associations, so to avoid
// races for now we initialize it here. It's better to
// be predictable anyways.
- Shell.AppMonitor.get_default();
+ Shell.WindowTracker.get_default();
+ Shell.AppUsage.get_default();
// The background color really only matters if there is no desktop
// window (say, nautilus) running. We set it mostly so things look good
this.actor.opacity = 192;
}));
- this._metaDisplay.connect('notify::focus-window', Lang.bind(this, this._sync));
-
- let appMonitor = Shell.AppMonitor.get_default();
- appMonitor.connect('startup-sequence-changed', Lang.bind(this, this._sync));
- // For now just resync on application add/remove; this is mainly to handle
+ let tracker = Shell.WindowTracker.get_default();
+ tracker.connect('notify::focus-app', Lang.bind(this, this._sync));
+ tracker.connect('startup-sequence-changed', Lang.bind(this, this._sync));
+ // For now just resync on all running state changes; this is mainly to handle
// cases where the focused window's application changes without the focus
// changing. An example case is how we map Firefox based on the window
// title which is a dynamic property.
- appMonitor.connect('app-added', Lang.bind(this, this._sync));
- appMonitor.connect('app-removed', Lang.bind(this, this._sync));
+ tracker.connect('app-running-changed', Lang.bind(this, this._sync));
this._sync();
},
_sync: function() {
- let appMonitor = Shell.AppMonitor.get_default();
+ let tracker = Shell.WindowTracker.get_default();
- let focusWindow = this._metaDisplay.get_focus_window();
- let focusedApp;
- if (focusWindow == null) {
- focusedApp = null;
- } else {
- focusedApp = appMonitor.get_window_app(focusWindow);
- }
+ let focusedApp = tracker.focus_app;
let lastSequence = null;
if (focusedApp == null) {
- let sequences = appMonitor.get_startup_sequences();
+ let sequences = tracker.get_startup_sequences();
if (sequences.length > 0)
lastSequence = sequences[sequences.length - 1];
}
this._iconBox.hide();
this._label.text = '';
if (this._focusedApp != null) {
- let info = focusedApp.get_info();
- let icon = info.create_icon_texture(PANEL_ICON_SIZE);
+ let icon = this._focusedApp.create_icon_texture(PANEL_ICON_SIZE);
this._iconBox.append(icon, Big.BoxPackFlags.NONE);
this._iconBox.show();
- this._label.text = info.get_name();
+ this._label.text = this._focusedApp.get_name();
} else if (this._activeSequence != null) {
let icon = this._activeSequence.create_icon(PANEL_ICON_SIZE);
this._iconBox.append(icon, Big.BoxPackFlags.NONE);
const Gettext = imports.gettext.domain('gnome-shell');
const _ = Gettext.gettext;
+const AppFavorites = imports.ui.appFavorites;
const DocInfo = imports.misc.docInfo;
const COLLAPSED_WIDTH = 24;
this.collapsedActor = new Big.Box({ spacing: 2});
let appSystem = Shell.AppSystem.get_default();
- let apps = appSystem.get_favorites();
+ let apps = AppFavorites.getAppFavorites().getFavorites();
for (let i = 0; i < apps.length; i++) {
- let app = appSystem.lookup_cached_app(apps[i]);
- if (!app)
- continue;
- this.addItem(new AppsWidgetInfo(app));
+ this.addItem(new AppsWidgetInfo(apps[i]));
}
}
};
* fashion by the order in which the windows were created.
*/
_orderWindowsByMotionAndStartup: function(windows, slots) {
- let appMonitor = Shell.AppMonitor.get_default();
windows.sort(function(w1, w2) {
return w2.get_stable_sequence() - w1.get_stable_sequence();
});
// Tests if @win should be shown in the Overview
_isOverviewWindow : function (win) {
- let appMon = Shell.AppMonitor.get_default()
- return appMon.is_window_usage_tracked(win.get_meta_window());
+ let tracker = Shell.WindowTracker.get_default()
+ return tracker.is_window_interesting(win.get_meta_window());
},
_createWindowIcon: function(window) {
- let appSys = Shell.AppSystem.get_default();
- let appMon = Shell.AppMonitor.get_default()
- let app = appMon.get_window_app(window.metaWindow);
+ let tracker = Shell.WindowTracker.get_default()
+ let app = tracker.get_window_app(window.metaWindow);
let iconTexture = null;
// The design is application based, so prefer the application
// icon here if we have it. FIXME - should move this fallback code
this._windowSelectionAppId = appId;
- let appSys = Shell.AppMonitor.get_default();
+ let appSys = Shell.AppSystem.get_default();
let showOnlyWindows = {};
- let windows = appSys.get_windows_for_app(appId);
+ let app = appSys.get_app(appId);
+ let windows = app.get_windows();
for (let i = 0; i < windows.length; i++) {
showOnlyWindows[windows[i]] = 1;
}
gnome-shell-plugin.c \
shell-app.c \
shell-app.h \
- shell-app-monitor.c \
- shell-app-monitor.h \
+ shell-app-private.h \
shell-app-system.c \
shell-app-system.h \
+ shell-app-usage.c \
+ shell-app-usage.h \
shell-arrow.c \
shell-arrow.h \
shell-button-box.c \
shell-texture-cache.h \
shell-uri-util.c \
shell-uri-util.h \
+ shell-window-tracker.c \
+ shell-window-tracker.h \
shell-wm.c \
shell-wm.h
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
-#include <string.h>
-#include <stdlib.h>
-
-#include <X11/Xlib.h>
-#include <X11/Xatom.h>
-#include <gdk/gdk.h>
-#include <gdk/gdkx.h>
-#include <gio/gio.h>
-#include <gconf/gconf.h>
-#include <gconf/gconf-client.h>
-#include <dbus/dbus-glib.h>
-
-#define SN_API_NOT_YET_FROZEN 1
-#include <libsn/sn.h>
-
-#include "shell-texture-cache.h"
-#include "shell-app-monitor.h"
-#include "shell-app-system.h"
-#include "shell-global.h"
-#include "shell-marshal.h"
-
-#include "display.h"
-#include "window.h"
-#include "group.h"
-
-/* This file includes modified code from
- * desktop-data-engine/engine-dbus/hippo-application-monitor.c
- * in the functions collecting application usage data.
- * Written by Owen Taylor, originally licensed under LGPL 2.1.
- * Copyright Red Hat, Inc. 2006-2008
- */
-
-/**
- * SECTION:shell-app-monitor
- * @short_description: Associate windows with application data and track usage/state data
- *
- * The application monitor has two primary purposes. First, it
- * maintains a mapping from windows to applications (.desktop file ids).
- * It currently implements this with some heuristics on the WM_CLASS X11
- * property (and some static override regexps); in the future, we want to
- * have it also track through startup-notification.
- *
- * Second, the monitor also maintains some usage and state statistics for
- * windows by keeping track of the approximate time an application's
- * windows are focus, as well as the last workspace it was seen on.
- * This time tracking is implemented by watching for restack notifications,
- * and computing a time delta between them. Also we monitor the
- * GNOME Session "StatusChanged" signal which by default is emitted after 5
- * minutes to signify idle.
- */
-
-#define APP_MONITOR_GCONF_DIR SHELL_GCONF_DIR"/app_monitor"
-#define ENABLE_MONITORING_KEY APP_MONITOR_GCONF_DIR"/enable_monitoring"
-
-#define FOCUS_TIME_MIN_SECONDS 7 /* Need 7 continuous seconds of focus */
-
-#define USAGE_CLEAN_DAYS 7 /* If after 7 days we haven't seen an app, purge it */
-
-#define SNAPSHOTS_PER_CLEAN_SECONDS ((USAGE_CLEAN_DAYS * 24 * 60 * 60) / MIN_SNAPSHOT_APP_SECONDS) /* One week */
-
-/* Data is saved to file SHELL_CONFIG_DIR/DATA_FILENAME */
-#define DATA_FILENAME "application_state"
-
-#define IDLE_TIME_TRANSITION_SECONDS 30 /* If we transition to idle, only count
- * this many seconds of usage */
-
-/* The ranking algorithm we use is: every time an app score reaches SCORE_MAX,
- * divide all scores by 2. Scores are raised by 1 unit every SAVE_APPS_TIMEOUT
- * seconds. This mechanism allows the list to update relatively fast when
- * a new app is used intensively.
- * To keep the list clean, and avoid being Big Brother, apps that have not been
- * seen for a week and whose score is below SCORE_MIN are removed.
- */
-
-/* How often we save internally app data, in seconds */
-#define SAVE_APPS_TIMEOUT_SECONDS 5 /* leave this low for testing, we can bump later if need be */
-
-/* With this value, an app goes from bottom to top of the
- * usage list in 50 hours of use */
-#define SCORE_MAX (3600 * 50 / FOCUS_TIME_MIN_SECONDS)
-
-/* If an app's score in lower than this and the app has not been used in a week,
- * remove it */
-#define SCORE_MIN (SCORE_MAX >> 3)
-
-/* Title patterns to detect apps that don't set WM class as needed.
- * Format: pseudo/wanted WM class, title regex pattern, NULL (for GRegex) */
-static struct
-{
- const char *app_id;
- const char *pattern;
- GRegex *regex;
-} title_patterns[] = {
- {"mozilla-firefox.desktop", ".* - Mozilla Firefox", NULL}, \
- {"openoffice.org-writer.desktop", ".* - OpenOffice.org Writer$", NULL}, \
- {"openoffice.org-calc.desktop", ".* - OpenOffice.org Calc$", NULL}, \
- {"openoffice.org-impress.desktop", ".* - OpenOffice.org Impress$", NULL}, \
- {"openoffice.org-draw.desktop", ".* - OpenOffice.org Draw$", NULL}, \
- {"openoffice.org-base.desktop", ".* - OpenOffice.org Base$", NULL}, \
- {"openoffice.org-math.desktop", ".* - OpenOffice.org Math$", NULL}, \
- {NULL, NULL, NULL}
-};
-
-
-typedef struct AppUsage AppUsage;
-typedef struct ActiveAppsData ActiveAppsData;
-
-struct _ShellAppMonitor
-{
- GObject parent;
-
- GFile *configfile;
- DBusGProxy *session_proxy;
- GdkDisplay *display;
- GConfClient *gconf_client;
- gulong last_idle;
- guint idle_focus_change_id;
- guint save_id;
- guint gconf_notify;
- gboolean currently_idle;
- gboolean enable_monitoring;
-
- /* See comment in AppUsage below */
- guint initially_seen_sequence;
-
- GSList *previously_running;
-
- long watch_start_time;
- MetaWindow *watched_window;
-
- /* <MetaWindow * window, ShellApp *app> */
- GHashTable *window_to_app;
-
- /* <const char *, ShellApp *app> */
- GHashTable *running_apps;
-
- /* <char *context, GHashTable<char *appid, AppUsage *usage>> */
- GHashTable *app_usages_for_context;
-};
-
-G_DEFINE_TYPE (ShellAppMonitor, shell_app_monitor, G_TYPE_OBJECT);
-
-/* Represents an application record for a given context */
-struct AppUsage
-{
- /* Whether the application we're tracking is "transient", see
- * shell_app_info_is_transient.
- */
- gboolean transient;
-
- gdouble score; /* Based on the number of times we'e seen the app and normalized */
- long last_seen; /* Used to clear old apps we've only seen a few times */
-
- /* how many windows are currently open; in terms of persistence we only save
- * whether the app had any windows or not. */
- guint window_count;
-
- /* Transient data */
- guint initially_seen_sequence; /* Arbitrary ordered integer for when we first saw
- * this application in this session. Used to order
- * the open applications.
- */
-};
-
-enum {
- APP_ADDED,
- APP_REMOVED,
- WINDOW_ADDED,
- WINDOW_REMOVED,
- STARTUP_SEQUENCE_CHANGED,
-
- LAST_SIGNAL
-};
-
-static guint signals[LAST_SIGNAL] = { 0 };
-
-static void shell_app_monitor_finalize (GObject *object);
-
-static void on_session_status_changed (DBusGProxy *proxy, guint status, ShellAppMonitor *monitor);
-static void on_focus_window_changed (MetaDisplay *display, GParamSpec *spec, ShellAppMonitor *monitor);
-static void ensure_queued_save (ShellAppMonitor *monitor);
-static AppUsage * get_app_usage_for_context_and_id (ShellAppMonitor *monitor,
- const char *context,
- const char *appid);
-
-static void track_window (ShellAppMonitor *monitor, MetaWindow *window);
-static void disassociate_window (ShellAppMonitor *monitor, MetaWindow *window);
-
-static gboolean idle_save_application_usage (gpointer data);
-
-static void restore_from_file (ShellAppMonitor *monitor);
-
-static void update_enable_monitoring (ShellAppMonitor *monitor);
-
-static void on_enable_monitoring_key_changed (GConfClient *client,
- guint connexion_id,
- GConfEntry *entry,
- gpointer monitor);
-
-static long
-get_time (void)
-{
- GTimeVal tv;
- g_get_current_time (&tv);
- return tv.tv_sec;
-}
-
-static void shell_app_monitor_class_init(ShellAppMonitorClass *klass)
-{
- GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-
- gobject_class->finalize = shell_app_monitor_finalize;
-
- signals[APP_ADDED] = g_signal_new ("app-added",
- SHELL_TYPE_APP_MONITOR,
- G_SIGNAL_RUN_LAST,
- 0,
- NULL, NULL,
- g_cclosure_marshal_VOID__OBJECT,
- G_TYPE_NONE, 1,
- SHELL_TYPE_APP);
- signals[APP_REMOVED] = g_signal_new ("app-removed",
- SHELL_TYPE_APP_MONITOR,
- G_SIGNAL_RUN_LAST,
- 0,
- NULL, NULL,
- g_cclosure_marshal_VOID__OBJECT,
- G_TYPE_NONE, 1,
- SHELL_TYPE_APP);
-
- signals[WINDOW_ADDED] = g_signal_new ("window-added",
- SHELL_TYPE_APP_MONITOR,
- G_SIGNAL_RUN_LAST,
- 0,
- NULL, NULL,
- _shell_marshal_VOID__OBJECT_OBJECT,
- G_TYPE_NONE, 2,
- SHELL_TYPE_APP,
- META_TYPE_WINDOW);
- signals[WINDOW_REMOVED] = g_signal_new ("window-removed",
- SHELL_TYPE_APP_MONITOR,
- G_SIGNAL_RUN_LAST,
- 0,
- NULL, NULL,
- _shell_marshal_VOID__OBJECT_OBJECT,
- G_TYPE_NONE, 2,
- SHELL_TYPE_APP,
- META_TYPE_WINDOW);
-
- signals[STARTUP_SEQUENCE_CHANGED] = g_signal_new ("startup-sequence-changed",
- SHELL_TYPE_APP_MONITOR,
- G_SIGNAL_RUN_LAST,
- 0,
- NULL, NULL,
- g_cclosure_marshal_VOID__BOXED,
- G_TYPE_NONE, 1, SHELL_TYPE_STARTUP_SEQUENCE);
-}
-
-static void
-destroy_usage (AppUsage *usage)
-{
- g_free (usage);
-}
-
-/**
- * get_app_id_from_title:
- *
- * Use a window's "title" property to determine an application ID.
- * This is a temporary crutch for a few applications until we get
- * them correctly setting their WM_CLASS.
- */
-static const char *
-get_app_id_from_title (MetaWindow *window)
-{
- static gboolean patterns_initialized = FALSE;
- char *title;
- int i;
-
- g_object_get (window, "title", &title, NULL);
-
- if (!patterns_initialized) /* Generate match patterns once for all */
- {
- patterns_initialized = TRUE;
- for (i = 0; title_patterns[i].app_id; i++)
- {
- title_patterns[i].regex = g_regex_new (title_patterns[i].pattern,
- 0, 0, NULL);
- }
- }
-
- /* Match window title patterns to identifiers for non-standard apps */
- if (title)
- {
- for (i = 0; title_patterns[i].app_id; i++)
- {
- if (g_regex_match (title_patterns[i].regex, title, 0, NULL))
- {
- g_free (title);
- /* Set a pseudo WM class, handled like true ones */
- return title_patterns[i].app_id;
- }
- }
- }
-
- g_free (title);
- return NULL;
-}
-
-/**
- * get_cleaned_wmclass_for_window:
- *
- * A "cleaned" wmclass is the WM_CLASS property of a window,
- * after some transformations to turn it into a form
- * somewhat more resilient to changes, such as lowercasing.
- */
-static char *
-get_cleaned_wmclass_for_window (MetaWindow *window)
-{
- const char *wmclass;
- char *cleaned_wmclass;
-
- wmclass = meta_window_get_wm_class (window);
- if (!wmclass)
- return NULL;
-
- cleaned_wmclass = g_utf8_strdown (wmclass, -1);
-
- /* This handles "Fedora Eclipse", probably others.
- * Note g_strdelimit is modify-in-place. */
- g_strdelimit (cleaned_wmclass, " ", '-');
-
- return cleaned_wmclass;
-}
-
-/**
- * window_is_tracked:
- *
- * Returns: %TRUE iff we want to scan this window for application association
- */
-static gboolean
-window_is_tracked (MetaWindow *window)
-{
- if (meta_window_is_override_redirect (window))
- return FALSE;
-
- return TRUE;
-}
-
-/**
- * shell_app_monitor_is_window_usage_tracked:
- *
- * Determine if it makes sense to track the given window for application
- * usage. An example of a window we don't want to track is the root
- * desktop window. We skip all override-redirect types, and also
- * exclude other window types like tooltip explicitly, though generally
- * most of these should be override-redirect.
- *
- * The usage data is also currently used to return the list of user-interesting
- * windows associated with an application.
- *
- * Returns: %TRUE iff we want to record focus time spent in this window
- */
-gboolean
-shell_app_monitor_is_window_usage_tracked (MetaWindow *window)
-{
- if (!window_is_tracked (window))
- return FALSE;
-
- if (meta_window_is_skip_taskbar (window))
- return FALSE;
-
- switch (meta_window_get_window_type (window))
- {
- /* Definitely ignore these. */
- case META_WINDOW_DESKTOP:
- case META_WINDOW_DOCK:
- case META_WINDOW_SPLASHSCREEN:
- /* Should have already been handled by override_redirect above,
- * but explicitly list here so we get the "unhandled enum"
- * warning if in the future anything is added.*/
- case META_WINDOW_DROPDOWN_MENU:
- case META_WINDOW_POPUP_MENU:
- case META_WINDOW_TOOLTIP:
- case META_WINDOW_NOTIFICATION:
- case META_WINDOW_COMBO:
- case META_WINDOW_DND:
- case META_WINDOW_OVERRIDE_OTHER:
- return FALSE;
- case META_WINDOW_NORMAL:
- case META_WINDOW_DIALOG:
- case META_WINDOW_MODAL_DIALOG:
- case META_WINDOW_MENU:
- case META_WINDOW_TOOLBAR:
- case META_WINDOW_UTILITY:
- break;
- }
-
- return TRUE;
-}
-
-/**
- * get_app_for_window_direct:
- *
- * Looks only at the given window, and attempts to determine
- * an application based on WM_CLASS. If that fails, then
- * a "transient" application is created.
- *
- * Return value: (transfer full): A newly-referenced #ShellApp
- */
-static ShellApp *
-get_app_for_window_direct (MetaWindow *window)
-{
- ShellApp *app;
- ShellAppInfo *appinfo;
- ShellAppSystem *appsys;
- char *wmclass;
- char *with_desktop;
-
- wmclass = get_cleaned_wmclass_for_window (window);
-
- if (!wmclass)
- return _shell_app_new_for_window (window);
-
- with_desktop = g_strjoin (NULL, wmclass, ".desktop", NULL);
- g_free (wmclass);
-
- appsys = shell_app_system_get_default ();
- appinfo = shell_app_system_lookup_heuristic_basename (appsys, with_desktop);
- g_free (with_desktop);
-
- if (appinfo == NULL)
- {
- const char *id = get_app_id_from_title (window);
-
- if (id != NULL)
- appinfo = shell_app_system_load_from_desktop_file (appsys, id, NULL);
- }
- if (appinfo == NULL)
- return _shell_app_new_for_window (window);
-
- app = _shell_app_new (appinfo);
- shell_app_info_unref (appinfo);
- return app;
-}
-
-/**
- * get_app_for_window:
- *
- * Determines the application associated with a window, using
- * all available information such as the window's MetaGroup,
- * and what we know about other windows.
- */
-static ShellApp *
-get_app_for_window (ShellAppMonitor *monitor,
- MetaWindow *window)
-{
- ShellApp *result;
- MetaWindow *source_window;
- GSList *group_windows;
- MetaGroup *group;
- GSList *iter;
-
- group = meta_window_get_group (window);
- if (group == NULL)
- group_windows = g_slist_prepend (NULL, window);
- else
- group_windows = meta_group_list_windows (group);
-
- source_window = window;
-
- result = NULL;
- /* Try finding a window in the group of type NORMAL; if we
- * succeed, use that as our source. */
- for (iter = group_windows; iter; iter = iter->next)
- {
- MetaWindow *group_window = iter->data;
-
- if (meta_window_get_window_type (group_window) != META_WINDOW_NORMAL)
- continue;
-
- source_window = group_window;
- result = g_hash_table_lookup (monitor->window_to_app, group_window);
- if (result)
- break;
- }
-
- g_slist_free (group_windows);
-
- if (result != NULL)
- {
- g_object_ref (result);
- return result;
- }
-
- return get_app_for_window_direct (source_window);
-}
-
-static const char *
-get_window_context (MetaWindow *window)
-{
- return "";
-}
-
-static const char *
-get_app_context (ShellApp *app)
-{
- return "";
-}
-
-static GHashTable *
-get_usages_for_context (ShellAppMonitor *monitor,
- const char *context)
-{
- GHashTable *context_usages;
-
- context_usages = g_hash_table_lookup (monitor->app_usages_for_context, context);
- if (context_usages == NULL)
- {
- context_usages = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)destroy_usage);
- g_hash_table_insert (monitor->app_usages_for_context, g_strdup (context),
- context_usages);
- }
- return context_usages;
-}
-
-static AppUsage *
-get_app_usage_for_context_and_id (ShellAppMonitor *monitor,
- const char *context,
- const char *appid)
-{
- AppUsage *usage;
- GHashTable *context_usages;
-
- context_usages = get_usages_for_context (monitor, context);
-
- usage = g_hash_table_lookup (context_usages, appid);
- if (usage)
- return usage;
-
- usage = g_new0 (AppUsage, 1);
- usage->initially_seen_sequence = ++monitor->initially_seen_sequence;
- g_hash_table_insert (context_usages, g_strdup (appid), usage);
-
- return usage;
-}
-
-static AppUsage *
-get_app_usage_from_window (ShellAppMonitor *monitor,
- MetaWindow *window)
-{
- ShellApp *app;
- const char *context;
-
- app = g_hash_table_lookup (monitor->window_to_app, window);
- if (!app)
- return NULL;
-
- context = get_window_context (window);
-
- return get_app_usage_for_context_and_id (monitor, context, shell_app_get_id (app));
-}
-
-static MetaWindow *
-get_active_window (ShellAppMonitor *monitor)
-{
- MetaScreen *screen;
- MetaDisplay *display;
- MetaWindow *window;
-
- screen = shell_global_get_screen (shell_global_get ());
- display = meta_screen_get_display (screen);
- window = meta_display_get_focus_window (display);
-
- if (window != NULL && shell_app_monitor_is_window_usage_tracked (window))
- return window;
- return NULL;
-}
-
-typedef struct {
- gboolean in_context;
- GHashTableIter context_iter;
- const char *context_id;
- GHashTableIter usage_iter;
-} UsageIterator;
-
-static void
-usage_iterator_init (ShellAppMonitor *self,
- UsageIterator *iter)
-{
- iter->in_context = FALSE;
- g_hash_table_iter_init (&(iter->context_iter), self->app_usages_for_context);
-}
-
-static gboolean
-usage_iterator_next (ShellAppMonitor *self,
- UsageIterator *iter,
- const char **context,
- const char **id,
- AppUsage **usage)
-{
- gpointer key, value;
- gboolean next_context;
-
- if (!iter->in_context)
- next_context = TRUE;
- else if (!g_hash_table_iter_next (&(iter->usage_iter), &key, &value))
- next_context = TRUE;
- else
- next_context = FALSE;
-
- while (next_context)
- {
- GHashTable *app_usages;
-
- if (!g_hash_table_iter_next (&(iter->context_iter), &key, &value))
- return FALSE;
- iter->in_context = TRUE;
- iter->context_id = key;
- app_usages = value;
- g_hash_table_iter_init (&(iter->usage_iter), app_usages);
-
- next_context = !g_hash_table_iter_next (&(iter->usage_iter), &key, &value);
- }
-
- *context = iter->context_id;
- *id = key;
- *usage = value;
-
- return TRUE;
-}
-
-static void
-usage_iterator_remove (ShellAppMonitor *self,
- UsageIterator *iter)
-{
- g_assert (iter->in_context);
-
- g_hash_table_iter_remove (&(iter->usage_iter));
-}
-
-/* Limit the score to a certain level so that most used apps can change */
-static void
-normalize_usage (ShellAppMonitor *self)
-{
- UsageIterator iter;
- const char *context;
- const char *id;
- AppUsage *usage;
-
- usage_iterator_init (self, &iter);
-
- while (usage_iterator_next (self, &iter, &context, &id, &usage))
- {
- usage->score /= 2;
- }
-}
-
-static void
-increment_usage_for_window_at_time (ShellAppMonitor *self,
- MetaWindow *window,
- long time)
-{
- AppUsage *usage;
- guint elapsed;
- guint usage_count;
-
- usage = get_app_usage_from_window (self, window);
-
- usage->last_seen = time;
-
- elapsed = time - self->watch_start_time;
- usage_count = elapsed / FOCUS_TIME_MIN_SECONDS;
- if (usage_count > 0)
- {
- usage->score += usage_count;
- if (usage->score > SCORE_MAX)
- normalize_usage (self);
- ensure_queued_save (self);
- }
-}
-
-static void
-increment_usage_for_window (ShellAppMonitor *self,
- MetaWindow *window)
-{
- long curtime = get_time ();
- increment_usage_for_window_at_time (self, window, curtime);
-}
-
-/**
- * reset_usage:
- *
- * For non-transient applications, just reset the "seen sequence".
- *
- * For transient ones, we don't want to keep an AppUsage around, so
- * remove it entirely.
- */
-static void
-reset_usage (ShellAppMonitor *self,
- const char *context,
- const char *appid,
- AppUsage *usage)
-{
- if (!usage->transient)
- {
- usage->initially_seen_sequence = 0;
- }
- else
- {
- GHashTable *usages;
- usages = g_hash_table_lookup (self->app_usages_for_context, context);
- g_hash_table_remove (usages, appid);
- }
-}
-
-static void
-on_transient_window_title_changed (MetaWindow *window,
- GParamSpec *spec,
- ShellAppMonitor *self)
-{
- ShellAppSystem *appsys;
- ShellAppInfo *new_app;
- const char *id;
-
- /* Check if we now have a mapping using the window title */
- id = get_app_id_from_title (window);
- if (id == NULL)
- return;
-
- appsys = shell_app_system_get_default ();
- new_app = shell_app_system_load_from_desktop_file (appsys, id, NULL);
- if (new_app == NULL)
- return;
- shell_app_info_unref (new_app);
-
- /* It's simplest to just treat this as a remove + add. */
- disassociate_window (self, window);
- track_window (self, window);
-}
-
-static void
-track_window (ShellAppMonitor *self,
- MetaWindow *window)
-{
- ShellApp *app;
- AppUsage *usage;
-
- if (!window_is_tracked (window))
- return;
-
- app = get_app_for_window (self, window);
- if (!app)
- return;
-
- /* At this point we've stored the association from window -> application */
- g_hash_table_insert (self->window_to_app, window, app);
-
- /* However, we don't want to record usage for all kinds of windows;
- * the desktop window is a prime example. If a window isn't usage
- * tracked it doesn't count for the purposes of an application
- * running.
- */
- if (!shell_app_monitor_is_window_usage_tracked (window))
- return;
-
- usage = get_app_usage_from_window (self, window);
- usage->transient = shell_app_info_is_transient (shell_app_get_info (app));
-
- if (usage->transient)
- {
- /* For a transient application, it's possible one of our title regexps
- * will match at a later time, i.e. the application may not have set
- * its title fully at the time it initially maps a window. Watch
- * for title changes and recompute the app.
- */
- g_signal_connect (window, "notify::title", G_CALLBACK (on_transient_window_title_changed), self);
- }
-
- _shell_app_add_window (app, window);
-
- /* Keep track of the number of windows open for this app, when it
- * switches between 0 and 1 we emit an app-added signal.
- */
- usage->window_count++;
- if (usage->initially_seen_sequence == 0)
- usage->initially_seen_sequence = ++self->initially_seen_sequence;
- usage->last_seen = get_time ();
- if (usage->window_count == 1)
- {
- /* key is owned by the app */
- g_hash_table_insert (self->running_apps, (char*)shell_app_get_id (app),
- app);
- g_signal_emit (self, signals[APP_ADDED], 0, app);
- }
-
- /* Emit window-added after app-added */
- g_signal_emit (self, signals[WINDOW_ADDED], 0, app, window);
-}
-
-static void
-shell_app_monitor_on_window_added (MetaWorkspace *workspace,
- MetaWindow *window,
- gpointer user_data)
-{
- ShellAppMonitor *self = SHELL_APP_MONITOR (user_data);
-
- track_window (self, window);
-}
-
-static void
-disassociate_window (ShellAppMonitor *self,
- MetaWindow *window)
-{
- ShellApp *app;
-
- app = g_hash_table_lookup (self->window_to_app, window);
- if (!app)
- return;
-
- g_object_ref (app);
-
- if (window == self->watched_window)
- self->watched_window = NULL;
-
- if (shell_app_monitor_is_window_usage_tracked (window))
- {
- AppUsage *usage;
- const char *context;
-
- context = get_window_context (window);
- usage = get_app_usage_from_window (self, window);
- usage->window_count--;
-
- g_hash_table_remove (self->window_to_app, window);
-
- _shell_app_remove_window (app, window);
-
- g_signal_emit (self, signals[WINDOW_REMOVED], 0, app, window);
-
- if (usage->window_count == 0)
- {
- const char *id = shell_app_get_id (app);
- g_hash_table_remove (self->running_apps, id);
- g_signal_emit (self, signals[APP_REMOVED], 0, app);
- reset_usage (self, context, id, usage);
- }
- }
- else
- g_hash_table_remove (self->window_to_app, window);
-
- g_object_unref (app);
-}
-
-static void
-shell_app_monitor_on_window_removed (MetaWorkspace *workspace,
- MetaWindow *window,
- gpointer user_data)
-{
- disassociate_window (SHELL_APP_MONITOR (user_data), window);
-}
-
-static void
-load_initial_windows (ShellAppMonitor *monitor)
-{
- GList *workspaces, *iter;
- MetaScreen *screen = shell_global_get_screen (shell_global_get ());
- workspaces = meta_screen_get_workspaces (screen);
-
- for (iter = workspaces; iter; iter = iter->next)
- {
- MetaWorkspace *workspace = iter->data;
- GList *windows = meta_workspace_list_windows (workspace);
- GList *window_iter;
-
- for (window_iter = windows; window_iter; window_iter = window_iter->next)
- {
- MetaWindow *window = window_iter->data;
- track_window (monitor, window);
- }
-
- g_list_free (windows);
- }
-}
-
-static void
-on_session_status_changed (DBusGProxy *proxy,
- guint status,
- ShellAppMonitor *monitor)
-{
- gboolean idle;
-
- idle = (status >= 3);
- if (monitor->currently_idle == idle)
- return;
-
- monitor->currently_idle = idle;
- if (idle)
- {
- long end_time;
-
- /* Resync the active window, it may have changed while
- * we were idle and ignoring focus changes.
- */
- monitor->watched_window = get_active_window (monitor);
-
- /* The GNOME Session signal we watch is 5 minutes, but that's a long
- * time for this purpose. Instead, just add a base 30 seconds.
- */
- if (monitor->watched_window)
- {
- end_time = monitor->watch_start_time + IDLE_TIME_TRANSITION_SECONDS;
- increment_usage_for_window_at_time (monitor, monitor->watched_window, end_time);
- }
- }
- else
- {
- /* Transitioning to !idle, reset the start time */
- monitor->watch_start_time = get_time ();
- }
-}
-
-/**
- * shell_app_monitor_get_windows_for_app:
- * @self:
- * @appid: Find windows for this id
- *
- * Returns the normal toplevel windows associated with the given application.
- *
- * Returns: (transfer container) (element-type MetaWindow): List of #MetaWindow corresponding to appid
- */
-GSList *
-shell_app_monitor_get_windows_for_app (ShellAppMonitor *self,
- const char *appid)
-{
- GHashTableIter iter;
- gpointer key, value;
- GSList *ret = NULL;
-
- g_hash_table_iter_init (&iter, self->window_to_app);
-
- while (g_hash_table_iter_next (&iter, &key, &value))
- {
- MetaWindow *window = key;
- ShellApp *app = value;
- const char *id = shell_app_get_id (app);
-
- if (!shell_app_monitor_is_window_usage_tracked (window))
- continue;
-
- if (strcmp (id, appid) != 0)
- continue;
-
- ret = g_slist_prepend (ret, window);
- }
- return ret;
-}
-
-static void
-shell_app_monitor_on_n_workspaces_changed (MetaScreen *screen,
- GParamSpec *pspec,
- gpointer user_data)
-{
- ShellAppMonitor *self = SHELL_APP_MONITOR (user_data);
- GList *workspaces, *iter;
-
- workspaces = meta_screen_get_workspaces (screen);
-
- for (iter = workspaces; iter; iter = iter->next)
- {
- MetaWorkspace *workspace = iter->data;
-
- /* This pair of disconnect/connect is idempotent if we were
- * already connected, while ensuring we get connected for
- * new workspaces.
- */
- g_signal_handlers_disconnect_by_func (workspace,
- shell_app_monitor_on_window_added,
- self);
- g_signal_handlers_disconnect_by_func (workspace,
- shell_app_monitor_on_window_removed,
- self);
-
- g_signal_connect (workspace, "window-added",
- G_CALLBACK (shell_app_monitor_on_window_added), self);
- g_signal_connect (workspace, "window-removed",
- G_CALLBACK (shell_app_monitor_on_window_removed), self);
- }
-}
-
-static void
-init_window_monitoring (ShellAppMonitor *self)
-{
- MetaDisplay *display;
- MetaScreen *screen = shell_global_get_screen (shell_global_get ());
-
- g_signal_connect (screen, "notify::n-workspaces",
- G_CALLBACK (shell_app_monitor_on_n_workspaces_changed), self);
- display = meta_screen_get_display (screen);
- g_signal_connect (display, "notify::focus-window",
- G_CALLBACK (on_focus_window_changed), self);
-
- shell_app_monitor_on_n_workspaces_changed (screen, NULL, self);
-}
-
-static void
-on_startup_sequence_changed (MetaScreen *screen,
- SnStartupSequence *sequence,
- ShellAppMonitor *self)
-{
- /* Just proxy the signal */
- g_signal_emit (G_OBJECT (self), signals[STARTUP_SEQUENCE_CHANGED], 0, sequence);
-}
-
-static void
-shell_app_monitor_init (ShellAppMonitor *self)
-{
- MetaScreen *screen;
- GdkDisplay *display;
- char *path;
- char *shell_config_dir;
- DBusGConnection *session_bus;
-
- /* FIXME: should we create as many monitors as there are GdkScreens? */
- display = gdk_display_get_default ();
- screen = shell_global_get_screen (shell_global_get ());
-
- session_bus = dbus_g_bus_get (DBUS_BUS_SESSION, NULL);
- self->session_proxy = dbus_g_proxy_new_for_name (session_bus, "org.gnome.SessionManager",
- "/org/gnome/SessionManager/Presence",
- "org.gnome.SessionManager");
- dbus_g_proxy_add_signal (self->session_proxy, "StatusChanged",
- G_TYPE_UINT, G_TYPE_INVALID, G_TYPE_INVALID);
- dbus_g_proxy_connect_signal (self->session_proxy, "StatusChanged",
- G_CALLBACK (on_session_status_changed), self, NULL);
-
- self->display = g_object_ref (display);
-
- self->last_idle = 0;
- self->currently_idle = FALSE;
- self->enable_monitoring = FALSE;
-
- self->app_usages_for_context = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
- (GDestroyNotify) g_hash_table_destroy);
-
- self->window_to_app = g_hash_table_new_full (g_direct_hash, g_direct_equal,
- NULL, (GDestroyNotify) g_object_unref);
-
- self->running_apps = g_hash_table_new (g_str_hash, g_str_equal);
-
- g_object_get (shell_global_get(), "configdir", &shell_config_dir, NULL),
- path = g_build_filename (shell_config_dir, DATA_FILENAME, NULL);
- g_free (shell_config_dir);
- self->configfile = g_file_new_for_path (path);
- g_free (path);
- restore_from_file (self);
-
- load_initial_windows (self);
- init_window_monitoring (self);
-
- g_signal_connect (G_OBJECT (screen), "startup-sequence-changed",
- G_CALLBACK (on_startup_sequence_changed), self);
-
- self->gconf_client = gconf_client_get_default ();
- gconf_client_add_dir (self->gconf_client, APP_MONITOR_GCONF_DIR,
- GCONF_CLIENT_PRELOAD_NONE, NULL);
- self->gconf_notify =
- gconf_client_notify_add (self->gconf_client, ENABLE_MONITORING_KEY,
- on_enable_monitoring_key_changed, self, NULL, NULL);
- update_enable_monitoring (self);
-}
-
-static void
-shell_app_monitor_finalize (GObject *object)
-{
- ShellAppMonitor *self = SHELL_APP_MONITOR (object);
- int i;
-
- if (self->save_id > 0)
- g_source_remove (self->save_id);
- gconf_client_notify_remove (self->gconf_client, self->gconf_notify);
- g_object_unref (self->gconf_client);
- g_object_unref (self->display);
- g_hash_table_destroy (self->running_apps);
- g_hash_table_destroy (self->app_usages_for_context);
- for (i = 0; title_patterns[i].app_id; i++)
- g_regex_unref (title_patterns[i].regex);
- g_object_unref (self->configfile);
-
- G_OBJECT_CLASS (shell_app_monitor_parent_class)->finalize(object);
-}
-
-/**
- * shell_app_monitor_get_most_used_apps:
- * @monitor: the app monitor instance to request
- * @context: Activity identifier
- * @max_count: how many applications are requested. Note that the actual
- * list size may be less, or NULL if not enough applications are registered.
- *
- * Get a list of desktop identifiers representing the most popular applications
- * for a given context.
- *
- * Returns: (element-type utf8) (transfer container): List of application desktop
- * identifiers
- */
-GList *
-shell_app_monitor_get_most_used_apps (ShellAppMonitor *monitor,
- const char *context,
- gint max_count)
-{
- GHashTable *usages;
- usages = g_hash_table_lookup (monitor->app_usages_for_context, context);
- if (usages == NULL)
- return NULL;
- return g_hash_table_get_keys (usages);
-}
-
-/**
- * shell_app_monitor_get_window_app
- * @monitor: An app monitor instance
- * @metawin: A #MetaWindow
- *
- * Returns: Application associated with window
- */
-ShellApp *
-shell_app_monitor_get_window_app (ShellAppMonitor *monitor,
- MetaWindow *metawin)
-{
- MetaWindow *transient_for;
- ShellApp *app;
-
- transient_for = meta_window_get_transient_for (metawin);
- if (transient_for != NULL)
- metawin = transient_for;
-
- app = g_hash_table_lookup (monitor->window_to_app, metawin);
- if (app)
- g_object_ref (app);
-
- return app;
-}
-
-/**
- * shell_app_monitor_get_running_apps:
- * @monitor: An app monitor instance
- * @context: Activity identifier
- *
- * Returns the set of applications which currently have at least one open
- * window in the given context. The returned list will be sorted
- * by shell_app_compare().
- *
- * Returns: (element-type ShellApp) (transfer container): Active applications
- */
-GSList *
-shell_app_monitor_get_running_apps (ShellAppMonitor *monitor,
- const char *context)
-{
- gpointer key, value;
- GSList *ret;
- GHashTableIter iter;
-
- g_hash_table_iter_init (&iter, monitor->running_apps);
-
- ret = NULL;
- while (g_hash_table_iter_next (&iter, &key, &value))
- {
- ShellApp *app = value;
-
- if (strcmp (context, get_app_context (app)) != 0)
- continue;
-
- ret = g_slist_prepend (ret, app);
- }
-
- ret = g_slist_sort (ret, (GCompareFunc)shell_app_compare);
-
- return ret;
-}
-
-/**
- * shell_app_monitor_get_app:
- * @montior:
- * @id: Application identifier
- *
- * For running applications, returns the existing instance
- * of the running application model object. Otherwise,
- * returns a new object.
- *
- * Returns: (transfer full): Application associated with id
- */
-ShellApp *
-shell_app_monitor_get_app (ShellAppMonitor *monitor,
- const char *id)
-{
- ShellApp *app;
- ShellAppInfo *info;
-
- app = g_hash_table_lookup (monitor->running_apps, id);
- if (app)
- return g_object_ref (app);
-
- info = shell_app_system_lookup_cached_app (shell_app_system_get_default(), id);
- if (!info)
- return NULL;
-
- app = _shell_app_new (info);
- shell_app_info_unref (info);
-
- return app;
-}
-
-/**
- * shell_app_monitor_get_favorites:
- * @monitor:
- *
- * Returns: (transfer full) (element-type ShellApp): List of favorite applications
- */
-GSList *
-shell_app_monitor_get_favorites (ShellAppMonitor *monitor)
-{
- GSList *apps = NULL;
- GList *favorite_ids, *iter;
-
- favorite_ids = shell_app_system_get_favorites (shell_app_system_get_default ());
- for (iter = favorite_ids; iter; iter = iter->next)
- {
- const char *id = iter->data;
- ShellApp *app;
-
- app = shell_app_monitor_get_app (monitor, id);
- if (app)
- apps = g_slist_prepend (apps, g_object_ref (app));
- }
- apps = g_slist_reverse (apps);
-
- return apps;
-}
-
-static gboolean
-idle_handle_focus_change (gpointer data)
-{
- ShellAppMonitor *monitor = data;
- long curtime = get_time ();
-
- if (monitor->watched_window != NULL)
- increment_usage_for_window (monitor, monitor->watched_window);
-
- monitor->watched_window = get_active_window (monitor);
- monitor->watch_start_time = curtime;
-
- monitor->idle_focus_change_id = 0;
- return FALSE;
-}
-
-static void
-on_focus_window_changed (MetaDisplay *display,
- GParamSpec *spec,
- ShellAppMonitor *self)
-{
- if (!self->enable_monitoring || self->currently_idle)
- return;
-
- if (self->idle_focus_change_id != 0)
- return;
-
- /* Defensively compress notifications here in case something is going berserk,
- * we'll at least use a bit less system resources. */
- self->idle_focus_change_id = g_timeout_add (250, idle_handle_focus_change, self);
-}
-
-static void
-ensure_queued_save (ShellAppMonitor *monitor)
-{
- if (monitor->save_id != 0)
- return;
- monitor->save_id = g_timeout_add_seconds (SAVE_APPS_TIMEOUT_SECONDS, idle_save_application_usage, monitor);
-}
-
-/* Used to sort highest scores at the top */
-static gint
-usage_sort_apps (gconstpointer data1,
- gconstpointer data2)
-{
- const AppUsage *u1 = data1;
- const AppUsage *u2 = data2;
-
- if (u1->score > u2->score)
- return -1;
- else if (u1->score == u2->score)
- return 0;
- else
- return 1;
-}
-
-/* Clean up apps we see rarely.
- * The logic behind this is that if an app was seen less than SCORE_MIN times
- * and not seen for a week, it can probably be forgotten about.
- * This should much reduce the size of the list and avoid 'pollution'. */
-static gboolean
-idle_clean_usage (ShellAppMonitor *monitor)
-{
- UsageIterator iter;
- const char *context;
- const char *id;
- AppUsage *usage;
- GDate *date;
- guint32 date_days;
-
- /* Subtract a week */
- date = g_date_new ();
- g_date_set_time_t (date, time (NULL));
- g_date_subtract_days (date, USAGE_CLEAN_DAYS);
- date_days = g_date_get_julian (date);
-
- usage_iterator_init (monitor, &iter);
-
- while (usage_iterator_next (monitor, &iter, &context, &id, &usage))
- {
- if ((usage->score < SCORE_MIN) &&
- (usage->last_seen < date_days))
- usage_iterator_remove (monitor, &iter);
- }
- g_date_free (date);
-
- return FALSE;
-}
-
-static gboolean
-write_escaped (GDataOutputStream *stream,
- const char *str,
- GError **error)
-{
- gboolean ret;
- char *quoted = g_markup_escape_text (str, -1);
- ret = g_data_output_stream_put_string (stream, quoted, NULL, error);
- g_free (quoted);
- return ret;
-}
-
-static gboolean
-write_attribute_string (GDataOutputStream *stream,
- const char *elt_name,
- const char *str,
- GError **error)
-{
- gboolean ret = FALSE;
- char *elt;
-
- elt = g_strdup_printf (" %s=\"", elt_name);
- ret = g_data_output_stream_put_string (stream, elt, NULL, error);
- g_free (elt);
- if (!ret) goto out;
-
- ret = write_escaped (stream, str, error);
- if (!ret) goto out;
-
- ret = g_data_output_stream_put_string (stream, "\"", NULL, error);
-
-out:
- return ret;
-}
-
-static gboolean
-write_attribute_uint (GDataOutputStream *stream,
- const char *elt_name,
- guint value,
- GError **error)
-{
- gboolean ret;
- char *buf;
-
- buf = g_strdup_printf ("%u", value);
- ret = write_attribute_string (stream, elt_name, buf, error);
- g_free (buf);
-
- return ret;
-}
-
-static gboolean
-write_attribute_double (GDataOutputStream *stream,
- const char *elt_name,
- double value,
- GError **error)
-{
- gchar buf[G_ASCII_DTOSTR_BUF_SIZE];
- gboolean ret;
-
- g_ascii_dtostr (buf, sizeof (buf), value);
- ret = write_attribute_string (stream, elt_name, buf, error);
-
- return ret;
-}
-
-/* Save app data lists to file */
-static gboolean
-idle_save_application_usage (gpointer data)
-{
- ShellAppMonitor *monitor = SHELL_APP_MONITOR (data);
- UsageIterator iter;
- const char *current_context;
- const char *context;
- const char *id;
- AppUsage *usage;
- GFileOutputStream *output;
- GOutputStream *buffered_output;
- GDataOutputStream *data_output;
- GError *error = NULL;
-
- monitor->save_id = 0;
-
- /* Parent directory is already created by shell-global */
- output = g_file_replace (monitor->configfile, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error);
- if (!output)
- {
- g_debug ("Could not save applications usage data: %s", error->message);
- g_error_free (error);
- return FALSE;
- }
- buffered_output = g_buffered_output_stream_new (G_OUTPUT_STREAM(output));
- g_object_unref (output);
- data_output = g_data_output_stream_new (G_OUTPUT_STREAM(buffered_output));
- g_object_unref (buffered_output);
-
- if (!g_data_output_stream_put_string (data_output, "<?xml version=\"1.0\"?>\n<application-state>\n", NULL, &error))
- goto out;
-
- usage_iterator_init (monitor, &iter);
-
- current_context = NULL;
- while (usage_iterator_next (monitor, &iter, &context, &id, &usage))
- {
- /* Don't save the fake apps we create for windows */
- if (usage->transient)
- continue;
-
- if (context != current_context)
- {
- if (current_context != NULL)
- {
- if (!g_data_output_stream_put_string (data_output, " </context>", NULL, &error))
- goto out;
- }
- current_context = context;
- if (!g_data_output_stream_put_string (data_output, " <context", NULL, &error))
- goto out;
- if (!write_attribute_string (data_output, "id", context, &error))
- goto out;
- if (!g_data_output_stream_put_string (data_output, ">\n", NULL, &error))
- goto out;
- }
- if (!g_data_output_stream_put_string (data_output, " <application", NULL, &error))
- goto out;
- if (!write_attribute_string (data_output, "id", id, &error))
- goto out;
- if (!write_attribute_uint (data_output, "open-window-count", usage->window_count > 0, &error))
- goto out;
-
- if (!write_attribute_double (data_output, "score", usage->score, &error))
- goto out;
- if (!write_attribute_uint (data_output, "last-seen", usage->last_seen, &error))
- goto out;
- if (!g_data_output_stream_put_string (data_output, "/>\n", NULL, &error))
- goto out;
- }
- if (current_context != NULL)
- {
- if (!g_data_output_stream_put_string (data_output, " </context>\n", NULL, &error))
- goto out;
- }
- if (!g_data_output_stream_put_string (data_output, "</application-state>\n", NULL, &error))
- goto out;
-
-out:
- if (!error)
- g_output_stream_close (G_OUTPUT_STREAM(data_output), NULL, &error);
- g_object_unref (data_output);
- if (error)
- {
- g_debug ("Could not save applications usage data: %s", error->message);
- g_error_free (error);
- }
- return FALSE;
-}
-
-typedef struct {
- ShellAppMonitor *monitor;
- char *context;
-} ParseData;
-
-static void
-shell_app_monitor_start_element_handler (GMarkupParseContext *context,
- const gchar *element_name,
- const gchar **attribute_names,
- const gchar **attribute_values,
- gpointer user_data,
- GError **error)
-{
- ParseData *data = user_data;
-
- if (strcmp (element_name, "application-state") == 0)
- {
- }
- else if (strcmp (element_name, "context") == 0)
- {
- char *context = NULL;
- const char **attribute;
- const char **value;
-
- for (attribute = attribute_names, value = attribute_values; *attribute; attribute++, value++)
- {
- if (strcmp (*attribute, "id") == 0)
- context = g_strdup (*value);
- }
- if (context < 0)
- {
- g_set_error (error,
- G_MARKUP_ERROR,
- G_MARKUP_ERROR_PARSE,
- "Missing attribute id on <%s> element",
- element_name);
- return;
- }
- data->context = context;
- }
- else if (strcmp (element_name, "application") == 0)
- {
- const char **attribute;
- const char **value;
- AppUsage *usage;
- char *appid = NULL;
- GHashTable *usage_table;
-
- for (attribute = attribute_names, value = attribute_values; *attribute; attribute++, value++)
- {
- if (strcmp (*attribute, "id") == 0)
- appid = g_strdup (*value);
- }
-
- if (!appid)
- {
- g_set_error (error,
- G_MARKUP_ERROR,
- G_MARKUP_ERROR_PARSE,
- "Missing attribute id on <%s> element",
- element_name);
- return;
- }
-
- usage_table = get_usages_for_context (data->monitor, data->context);
-
- usage = g_new0 (AppUsage, 1);
- usage->initially_seen_sequence = 0;
- g_hash_table_insert (usage_table, appid, usage);
-
- for (attribute = attribute_names, value = attribute_values; *attribute; attribute++, value++)
- {
- if (strcmp (*attribute, "open-window-count") == 0)
- {
- guint count = strtoul (*value, NULL, 10);
- if (count > 0)
- data->monitor->previously_running = g_slist_prepend (data->monitor->previously_running,
- usage);
- }
- else if (strcmp (*attribute, "score") == 0)
- {
- usage->score = g_ascii_strtod (*value, NULL);
- }
- else if (strcmp (*attribute, "last-seen") == 0)
- {
- usage->last_seen = (guint) g_ascii_strtoull (*value, NULL, 10);
- }
- }
- }
- else
- {
- g_set_error (error,
- G_MARKUP_ERROR,
- G_MARKUP_ERROR_PARSE,
- "Unknown element <%s>",
- element_name);
- }
-}
-
-static void
-shell_app_monitor_end_element_handler (GMarkupParseContext *context,
- const gchar *element_name,
- gpointer user_data,
- GError **error)
-{
- ParseData *data = user_data;
-
- if (strcmp (element_name, "context") == 0)
- {
- g_free (data->context);
- data->context = NULL;
- }
-}
-
-static void
-shell_app_monitor_text_handler (GMarkupParseContext *context,
- const gchar *text,
- gsize text_len,
- gpointer user_data,
- GError **error)
-{
- /* do nothing, very very fast */
-}
-
-static GMarkupParser app_state_parse_funcs =
-{
- shell_app_monitor_start_element_handler,
- shell_app_monitor_end_element_handler,
- shell_app_monitor_text_handler,
- NULL,
- NULL
-};
-
-/* Load data about apps usage from file */
-static void
-restore_from_file (ShellAppMonitor *monitor)
-{
- GFileInputStream *input;
- ParseData parse_data;
- GMarkupParseContext *parse_context;
- GError *error = NULL;
- char buf[1024];
-
- input = g_file_read (monitor->configfile, NULL, &error);
- if (error)
- {
- if (error->code != G_IO_ERROR_NOT_FOUND)
- g_warning ("Could not load applications usage data: %s", error->message);
-
- g_error_free (error);
- return;
- }
-
- memset (&parse_data, 0, sizeof (ParseData));
- parse_data.monitor = monitor;
- parse_data.context = NULL;
- parse_context = g_markup_parse_context_new (&app_state_parse_funcs, 0, &parse_data, NULL);
-
- while (TRUE)
- {
- gssize count = g_input_stream_read ((GInputStream*) input, buf, sizeof(buf), NULL, &error);
- if (count <= 0)
- goto out;
- if (!g_markup_parse_context_parse (parse_context, buf, count, &error))
- goto out;
- }
-
-out:
- g_free (parse_data.context);
- g_markup_parse_context_free (parse_context);
- g_input_stream_close ((GInputStream*)input, NULL, NULL);
- g_object_unref (input);
-
- idle_clean_usage (monitor);
- monitor->previously_running = g_slist_sort (monitor->previously_running, usage_sort_apps);
-
- if (error)
- {
- g_warning ("Could not load applications usage data: %s", error->message);
- g_error_free (error);
- }
-}
-
-/* Enable or disable the timers, depending on the value of ENABLE_MONITORING_KEY
- * and taking care of the previous state. If monitoring is disabled, we still
- * report apps usage based on (possibly) saved data, but don't collect data.
- */
-static void
-update_enable_monitoring (ShellAppMonitor *monitor)
-{
- GConfValue *value;
- gboolean enable;
-
- value = gconf_client_get (monitor->gconf_client, ENABLE_MONITORING_KEY, NULL);
- if (value)
- {
- enable = gconf_value_get_bool (value);
- gconf_value_free (value);
- }
- else /* Schema is not present, set default value by hand to avoid getting FALSE */
- enable = TRUE;
-
- /* Be sure not to start the timers if they were already set */
- if (enable && !monitor->enable_monitoring)
- {
- on_focus_window_changed (NULL, NULL, monitor);
- }
- /* ...and don't try to stop them if they were not running */
- else if (!enable && monitor->enable_monitoring)
- {
- monitor->watched_window = NULL;
- if (monitor->save_id)
- {
- g_source_remove (monitor->save_id);
- monitor->save_id = 0;
- }
- }
-
- monitor->enable_monitoring = enable;
-}
-
-/* Called when the ENABLE_MONITORING_KEY boolean has changed */
-static void
-on_enable_monitoring_key_changed (GConfClient *client,
- guint connexion_id,
- GConfEntry *entry,
- gpointer monitor)
-{
- update_enable_monitoring ((ShellAppMonitor *) monitor);
-}
-
-
-/**
- * shell_app_monitor_get_startup_sequences:
- * @self:
- *
- * Returns: (transfer none) (element-type ShellStartupSequence): Currently active startup sequences
- */
-GSList *
-shell_app_monitor_get_startup_sequences (ShellAppMonitor *self)
-{
- ShellGlobal *global = shell_global_get ();
- MetaScreen *screen = shell_global_get_screen (global);
- return meta_screen_get_startup_sequences (screen);
-}
-
-/* sn_startup_sequence_ref returns void, so make a
- * wrapper which returns self */
-static SnStartupSequence *
-sequence_ref (SnStartupSequence *sequence)
-{
- sn_startup_sequence_ref (sequence);
- return sequence;
-}
-
-GType
-shell_startup_sequence_get_type (void)
-{
- static GType gtype = G_TYPE_INVALID;
- if (gtype == G_TYPE_INVALID)
- {
- gtype = g_boxed_type_register_static ("ShellStartupSequence",
- (GBoxedCopyFunc)sequence_ref,
- (GBoxedFreeFunc)sn_startup_sequence_unref);
- }
- return gtype;
-}
-
-const char *
-shell_startup_sequence_get_id (ShellStartupSequence *sequence)
-{
- return sn_startup_sequence_get_id ((SnStartupSequence*)sequence);
-}
-
-const char *
-shell_startup_sequence_get_name (ShellStartupSequence *sequence)
-{
- return sn_startup_sequence_get_name ((SnStartupSequence*)sequence);
-}
-
-gboolean
-shell_startup_sequence_get_completed (ShellStartupSequence *sequence)
-{
- return sn_startup_sequence_get_completed ((SnStartupSequence*)sequence);
-}
-
-/**
- * shell_startup_sequence_create_icon:
- * @sequence:
- * @size: Size in pixels of icon
- *
- * Returns: (transfer none): A new #ClutterTexture containing an icon for the sequence
- */
-ClutterActor *
-shell_startup_sequence_create_icon (ShellStartupSequence *sequence, guint size)
-{
- GThemedIcon *themed;
- const char *icon_name;
- ClutterActor *texture;
-
- icon_name = sn_startup_sequence_get_icon_name ((SnStartupSequence*)sequence);
- if (!icon_name)
- {
- texture = clutter_texture_new ();
- clutter_actor_set_size (texture, size, size);
- return texture;
- }
-
- themed = (GThemedIcon*)g_themed_icon_new (icon_name);
- texture = shell_texture_cache_load_gicon (shell_texture_cache_get_default (),
- G_ICON (themed), size);
- g_object_unref (G_OBJECT (themed));
- return texture;
-}
-
-/**
- * shell_app_monitor_get_default:
- *
- * Return Value: (transfer none): The global #ShellAppMonitor instance
- */
-ShellAppMonitor *
-shell_app_monitor_get_default ()
-{
- static ShellAppMonitor *instance;
-
- if (instance == NULL)
- instance = g_object_new (SHELL_TYPE_APP_MONITOR, NULL);
-
- return instance;
-}
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
-#ifndef __SHELL_APP_MONITOR_H__
-#define __SHELL_APP_MONITOR_H__
-
-#include <glib-object.h>
-#include <glib.h>
-
-#include "window.h"
-#include "shell-app.h"
-#include "shell-app-system.h"
-
-/*
- * This object provides monitoring of system application directories (.desktop files)
- * and activity-based statistics about applications usage
- */
-
-G_BEGIN_DECLS
-
-typedef struct _ShellAppMonitor ShellAppMonitor;
-typedef struct _ShellAppMonitorClass ShellAppMonitorClass;
-typedef struct _ShellAppMonitorPrivate ShellAppMonitorPrivate;
-
-#define SHELL_TYPE_APP_MONITOR (shell_app_monitor_get_type ())
-#define SHELL_APP_MONITOR(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SHELL_TYPE_APP_MONITOR, ShellAppMonitor))
-#define SHELL_APP_MONITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_APP_MONITOR, ShellAppMonitorClass))
-#define SHELL_IS_APP_MONITOR(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SHELL_TYPE_APP_MONITOR))
-#define SHELL_IS_APP_MONITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_APP_MONITOR))
-#define SHELL_APP_MONITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_APP_MONITOR, ShellAppMonitorClass))
-
-struct _ShellAppMonitorClass
-{
- GObjectClass parent_class;
-};
-
-GType shell_app_monitor_get_type (void) G_GNUC_CONST;
-
-ShellAppMonitor* shell_app_monitor_get_default(void);
-
-ShellApp *shell_app_monitor_get_window_app (ShellAppMonitor *monitor, MetaWindow *metawin);
-
-ShellApp *shell_app_monitor_get_app (ShellAppMonitor *monitor, const char *id);
-
-GSList *shell_app_monitor_get_favorites (ShellAppMonitor *monitor);
-
-GList *shell_app_monitor_get_most_used_apps (ShellAppMonitor *monitor,
- const char *context,
- gint number);
-
-GSList *shell_app_monitor_get_windows_for_app (ShellAppMonitor *monitor, const char *appid);
-
-gboolean shell_app_monitor_is_window_usage_tracked (MetaWindow *window);
-
-/* Get whatever's running right now */
-GSList *shell_app_monitor_get_running_apps (ShellAppMonitor *monitor, const char *context);
-
-GSList *shell_app_monitor_get_startup_sequences (ShellAppMonitor *monitor);
-
-/* Hidden typedef for SnStartupSequence */
-typedef struct _ShellStartupSequence ShellStartupSequence;
-#define SHELL_TYPE_STARTUP_SEQUENCE (shell_startup_sequence_get_type ())
-GType shell_startup_sequence_get_type (void);
-
-const char *shell_startup_sequence_get_id (ShellStartupSequence *sequence);
-const char *shell_startup_sequence_get_name (ShellStartupSequence *sequence);
-gboolean shell_startup_sequence_get_completed (ShellStartupSequence *sequence);
-ClutterActor *shell_startup_sequence_create_icon (ShellStartupSequence *sequence, guint size);
-
-G_END_DECLS
-
-#endif /* __SHELL_APP_MONITOR_H__ */
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_APP_PRIVATE_H__
+#define __SHELL_APP_PRIVATE_H__
+
+#include "shell-app.h"
+#include "shell-app-system.h"
+
+G_BEGIN_DECLS
+
+ShellAppInfo *_shell_app_get_info (ShellApp *app);
+
+ShellApp* _shell_app_new_for_window (MetaWindow *window);
+
+ShellApp* _shell_app_new (ShellAppInfo *appinfo);
+
+void _shell_app_add_window (ShellApp *app, MetaWindow *window);
+
+void _shell_app_remove_window (ShellApp *app, MetaWindow *window);
+
+G_END_DECLS
+
+#endif /* __SHELL_APP_PRIVATE_H__ */
#include <gconf/gconf-client.h>
#include <clutter/clutter.h>
+#include "shell-app-private.h"
#include "shell-global.h"
#include "shell-texture-cache.h"
#include "display.h"
enum {
INSTALLED_CHANGED,
- FAVORITES_CHANGED,
LAST_SIGNAL
};
GMenuTree *apps_tree;
GMenuTree *settings_tree;
+ GHashTable *app_id_to_info;
GHashTable *app_id_to_app;
GHashTable *cached_menu_contents; /* <char *id, GSList<ShellAppInfo*>> */
GSList *cached_settings; /* ShellAppInfo */
- GList *cached_favorites; /* utf8 */
-
gint app_monitor_id;
guint app_change_timeout_id;
static gboolean on_tree_changed (gpointer user_data);
static void on_tree_changed_cb (GMenuTree *tree, gpointer user_data);
static void reread_menus (ShellAppSystem *self);
-static void on_favorite_apps_changed (GConfClient *client, guint id, GConfEntry *entry, gpointer user_data);
-static void reread_favorite_apps (ShellAppSystem *system);
G_DEFINE_TYPE(ShellAppSystem, shell_app_system, G_TYPE_OBJECT);
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
- signals[FAVORITES_CHANGED] =
- g_signal_new ("favorites-changed",
- SHELL_TYPE_APP_SYSTEM,
- G_SIGNAL_RUN_LAST,
- G_STRUCT_OFFSET (ShellAppSystemClass, favorites_changed),
- NULL, NULL,
- g_cclosure_marshal_VOID__VOID,
- G_TYPE_NONE, 0);
g_type_class_add_private (gobject_class, sizeof (ShellAppSystemPrivate));
}
ShellAppSystemPrivate);
/* The key is owned by the value */
- priv->app_id_to_app = g_hash_table_new_full (g_str_hash, g_str_equal,
- NULL, (GDestroyNotify) shell_app_info_unref);
+ priv->app_id_to_info = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, (GDestroyNotify) shell_app_info_unref);
+
+ /* Key is owned by info */
+ priv->app_id_to_app = g_hash_table_new (g_str_hash, g_str_equal);
priv->cached_menu_contents = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, free_appinfo_gslist);
reread_menus (self);
client = gconf_client_get_default ();
-
- self->priv->app_monitor_id = gconf_client_notify_add (client, SHELL_APP_FAVORITES_KEY,
- on_favorite_apps_changed, self, NULL, NULL);
- reread_favorite_apps (self);
}
static void
g_hash_table_destroy (priv->cached_menu_contents);
+ g_hash_table_destroy (priv->app_id_to_info);
g_hash_table_destroy (priv->app_id_to_app);
g_slist_foreach (priv->cached_app_menus, (GFunc)shell_app_menu_entry_free, NULL);
g_slist_free (priv->cached_settings);
priv->cached_settings = NULL;
- g_list_free (priv->cached_favorites);
-
- gconf_client_notify_remove (gconf_client_get_default (), priv->app_monitor_id);
-
G_OBJECT_CLASS (shell_app_system_parent_class)->finalize(object);
}
if (ref)
shell_app_info_ref (info);
/* the name is owned by the info itself */
- g_hash_table_insert (self->priv->app_id_to_app, (char*)shell_app_info_get_id (info),
+ g_hash_table_insert (self->priv->app_id_to_info, (char*)shell_app_info_get_id (info),
info);
}
}
/* Now loop over applications.menu and settings.menu, inserting each by desktop file
* ID into a hash */
- g_hash_table_remove_all (self->priv->app_id_to_app);
+ g_hash_table_remove_all (self->priv->app_id_to_info);
trunk = gmenu_tree_get_root_directory (self->priv->apps_tree);
apps = gather_entries_recurse (self, NULL, trunk);
gmenu_tree_item_unref (trunk);
self, NULL);
}
-static GList *
-convert_gconf_value_string_list_to_list_uniquify (GConfValue *value )
-{
- GSList *list;
- GSList *tmp;
- GList *result = NULL;
- GHashTable *tmp_table = g_hash_table_new (g_str_hash, g_str_equal);
-
- list = gconf_value_get_list (value);
-
- for (tmp = list ; tmp; tmp = tmp->next)
- {
- GConfValue *value = tmp->data;
- char *str = g_strdup (gconf_value_get_string (value));
- if (!str)
- continue;
- if (g_hash_table_lookup (tmp_table, str))
- {
- g_free (str);
- continue;
- }
- g_hash_table_insert (tmp_table, str, GUINT_TO_POINTER(1));
- result = g_list_prepend (result, str);
- }
- g_hash_table_destroy (tmp_table);
- return g_list_reverse (result);
-}
-
-static void
-reread_favorite_apps (ShellAppSystem *system)
-{
- GConfClient *client = gconf_client_get_default ();
- GConfValue *val;
-
- val = gconf_client_get (client, SHELL_APP_FAVORITES_KEY, NULL);
-
- if (!(val && val->type == GCONF_VALUE_LIST && gconf_value_get_list_type (val) == GCONF_VALUE_STRING))
- return;
-
- g_list_foreach (system->priv->cached_favorites, (GFunc) g_free, NULL);
- g_list_free (system->priv->cached_favorites);
- system->priv->cached_favorites = convert_gconf_value_string_list_to_list_uniquify (val);
-
- gconf_value_free (val);
-}
-
-void
-on_favorite_apps_changed (GConfClient *client,
- guint id,
- GConfEntry *entry,
- gpointer user_data)
-{
- ShellAppSystem *system = SHELL_APP_SYSTEM (user_data);
- reread_favorite_apps (system);
- g_signal_emit (G_OBJECT (system), signals[FAVORITES_CHANGED], 0);
-}
-
GType
shell_app_info_get_type (void)
{
return instance;
}
-/**
- * shell_app_system_get_favorites:
- *
- * Return the list of applications which have been explicitly added to the
- * favorites.
- *
- * Return value: (transfer none) (element-type utf8): List of favorite application ids
- */
-GList *
-shell_app_system_get_favorites (ShellAppSystem *system)
-{
- return system->priv->cached_favorites;
-}
+typedef struct {
+ ShellAppSystem *appsys;
+ ShellAppInfo *info;
+} ShellAppRef;
static void
-set_gconf_value_string_list (GConfValue *val, GList *items)
+shell_app_system_on_app_weakref (gpointer data,
+ GObject *location)
{
- GList *iter;
- GSList *tmp = NULL;
-
- for (iter = items; iter; iter = iter->next)
- {
- const char *str = iter->data;
- GConfValue *strval = gconf_value_new (GCONF_VALUE_STRING);
- gconf_value_set_string (strval, str);
- tmp = g_slist_prepend (tmp, strval);
- }
- tmp = g_slist_reverse (tmp);
+ ShellAppRef *ref = data;
- gconf_value_set_list (val, tmp);
- g_slist_free (tmp);
+ g_hash_table_remove (ref->appsys->priv->app_id_to_app, shell_app_info_get_id (ref->info));
+ shell_app_info_unref (ref->info);
+ g_free (ref);
}
-void
-shell_app_system_add_favorite (ShellAppSystem *system, const char *id)
+/**
+ * shell_app_system_get_app:
+ *
+ * Find or create a #ShellApp corresponding to an id; if already cached
+ * elsewhere in memory, return that instance. Otherwise, create a new
+ * one.
+ *
+ * Return value: (transfer full): The #ShellApp for id, or %NULL if none
+ */
+ShellApp *
+shell_app_system_get_app (ShellAppSystem *self,
+ const char *id)
{
- GConfClient *client = gconf_client_get_default ();
- GConfValue *val;
- GList *iter;
+ ShellAppInfo *info;
+ ShellApp *app;
- iter = g_list_find_custom (system->priv->cached_favorites, id, (GCompareFunc)strcmp);
- if (iter)
- return;
+ app = g_hash_table_lookup (self->priv->app_id_to_app, id);
+ if (app)
+ return g_object_ref (app);
- val = gconf_value_new (GCONF_VALUE_LIST);
- gconf_value_set_list_type (val, GCONF_VALUE_STRING);
+ info = g_hash_table_lookup (self->priv->app_id_to_info, id);
+ if (!info)
+ return NULL;
- system->priv->cached_favorites = g_list_append (system->priv->cached_favorites, g_strdup (id));
+ app = _shell_app_new (info);
- set_gconf_value_string_list (val, system->priv->cached_favorites);
- gconf_client_set (client, SHELL_APP_FAVORITES_KEY, val, NULL);
+ return app;
}
+/* ShellAppSystem ensures we have a unique instance of
+ * apps per id.
+ */
void
-shell_app_system_remove_favorite (ShellAppSystem *system, const char *id)
+_shell_app_system_register_app (ShellAppSystem *self,
+ ShellApp *app)
{
- GConfClient *client = gconf_client_get_default ();
- GConfValue *val;
- GList *iter;
-
- iter = g_list_find_custom (system->priv->cached_favorites, id, (GCompareFunc)strcmp);
- if (!iter)
- return;
- g_free (iter->data);
- system->priv->cached_favorites = g_list_delete_link (system->priv->cached_favorites, iter);
+ const char *id;
+ ShellAppRef *ref;
- val = gconf_value_new (GCONF_VALUE_LIST);
- gconf_value_set_list_type (val, GCONF_VALUE_STRING);
+ id = shell_app_get_id (app);
- set_gconf_value_string_list (val, system->priv->cached_favorites);
- gconf_client_set (client, SHELL_APP_FAVORITES_KEY, val, NULL);
-}
-
-/**
- * shell_app_system_lookup_app:
- *
- * Return value: (transfer full): The #ShellAppInfo for id, or %NULL if none
- */
-ShellAppInfo *
-shell_app_system_lookup_cached_app (ShellAppSystem *self, const char *id)
-{
- ShellAppInfo *info;
+ g_return_if_fail (g_hash_table_lookup (self->priv->app_id_to_app, id) == NULL);
- info = g_hash_table_lookup (self->priv->app_id_to_app, id);
- if (info)
- shell_app_info_ref (info);
- return info;
+ ref = g_new0 (ShellAppRef, 1);
+ ref->appsys = self;
+ ref->info = shell_app_info_ref (_shell_app_get_info (app));
+ g_hash_table_insert (self->priv->app_id_to_app, (char*)shell_app_info_get_id (ref->info), app);
+ g_object_weak_ref (G_OBJECT (app), shell_app_system_on_app_weakref, ref);
}
ShellAppInfo *
* heuristically determined application identifier
* string, or %NULL if none.
*
- * Returns: (transfer full): A #ShellAppInfo for name
+ * Returns: (transfer full): A #ShellApp for name
*/
-ShellAppInfo *
+ShellApp *
shell_app_system_lookup_heuristic_basename (ShellAppSystem *system,
const char *name)
{
- ShellAppInfo *result;
+ ShellApp *result;
char **vendor_prefixes;
- result = shell_app_system_lookup_cached_app (system, name);
+ result = shell_app_system_get_app (system, name);
if (result != NULL)
return result;
*vendor_prefixes; vendor_prefixes++)
{
char *tmpid = g_strjoin (NULL, *vendor_prefixes, "-", name, NULL);
- result = shell_app_system_lookup_cached_app (system, tmpid);
+ result = shell_app_system_get_app (system, tmpid);
g_free (tmpid);
if (result != NULL)
return result;
#include <gio/gio.h>
#include <clutter/clutter.h>
+#include "shell-app.h"
#include "window.h"
#define SHELL_TYPE_APP_SYSTEM (shell_app_system_get_type ())
ShellAppInfo *shell_app_system_load_from_desktop_file (ShellAppSystem *system, const char *filename, GError **error);
-ShellAppInfo *shell_app_system_lookup_cached_app (ShellAppSystem *system, const char *id);
+ShellApp *shell_app_system_get_app (ShellAppSystem *system, const char *id);
-ShellAppInfo *shell_app_system_lookup_heuristic_basename (ShellAppSystem *system, const char *id);
+void _shell_app_system_register_app (ShellAppSystem *self, ShellApp *app);
+
+ShellApp *shell_app_system_lookup_heuristic_basename (ShellAppSystem *system, const char *id);
ShellAppInfo *shell_app_system_create_from_window (ShellAppSystem *system, MetaWindow *window);
GSList *shell_app_system_get_all_settings (ShellAppSystem *system);
-GList *shell_app_system_get_favorites (ShellAppSystem *system);
-
-void shell_app_system_add_favorite (ShellAppSystem *system, const char *id);
-
-void shell_app_system_remove_favorite (ShellAppSystem *system, const char *id);
-
#endif /* __SHELL_APP_SYSTEM_H__ */
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#include <string.h>
+#include <stdlib.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <gio/gio.h>
+#include <gconf/gconf.h>
+#include <gconf/gconf-client.h>
+#include <dbus/dbus-glib.h>
+
+#include "shell-app-usage.h"
+#include "shell-window-tracker.h"
+#include "shell-global.h"
+#include "shell-marshal.h"
+
+#include "display.h"
+#include "window.h"
+#include "group.h"
+
+/* This file includes modified code from
+ * desktop-data-engine/engine-dbus/hippo-application-monitor.c
+ * in the functions collecting application usage data.
+ * Written by Owen Taylor, originally licensed under LGPL 2.1.
+ * Copyright Red Hat, Inc. 2006-2008
+ */
+
+/**
+ * SECTION:shell-app-usage
+ * @short_description: Track application usage/state data
+ *
+ * This class maintains some usage and state statistics for
+ * applications by keeping track of the approximate time an application's
+ * windows are focused, as well as the last workspace it was seen on.
+ * This time tracking is implemented by watching for focus notifications,
+ * and computing a time delta between them. Also we watch the
+ * GNOME Session "StatusChanged" signal which by default is emitted after 5
+ * minutes to signify idle.
+ */
+
+#define APP_MONITOR_GCONF_DIR SHELL_GCONF_DIR"/app_monitor"
+#define ENABLE_MONITORING_KEY APP_MONITOR_GCONF_DIR"/enable_monitoring"
+
+#define FOCUS_TIME_MIN_SECONDS 7 /* Need 7 continuous seconds of focus */
+
+#define USAGE_CLEAN_DAYS 7 /* If after 7 days we haven't seen an app, purge it */
+
+/* Data is saved to file SHELL_CONFIG_DIR/DATA_FILENAME */
+#define DATA_FILENAME "application_state"
+
+#define IDLE_TIME_TRANSITION_SECONDS 30 /* If we transition to idle, only count
+ * this many seconds of usage */
+
+/* The ranking algorithm we use is: every time an app score reaches SCORE_MAX,
+ * divide all scores by 2. Scores are raised by 1 unit every SAVE_APPS_TIMEOUT
+ * seconds. This mechanism allows the list to update relatively fast when
+ * a new app is used intensively.
+ * To keep the list clean, and avoid being Big Brother, apps that have not been
+ * seen for a week and whose score is below SCORE_MIN are removed.
+ */
+
+/* How often we save internally app data, in seconds */
+#define SAVE_APPS_TIMEOUT_SECONDS 5 /* leave this low for testing, we can bump later if need be */
+
+/* With this value, an app goes from bottom to top of the
+ * usage list in 50 hours of use */
+#define SCORE_MAX (3600 * 50 / FOCUS_TIME_MIN_SECONDS)
+
+/* If an app's score in lower than this and the app has not been used in a week,
+ * remove it */
+#define SCORE_MIN (SCORE_MAX >> 3)
+
+/* http://www.gnome.org/~mccann/gnome-session/docs/gnome-session.html#org.gnome.SessionManager.Presence */
+#define GNOME_SESSION_STATUS_IDLE 3
+
+typedef struct UsageData UsageData;
+
+struct _ShellAppUsage
+{
+ GObject parent;
+
+ GFile *configfile;
+ DBusGProxy *session_proxy;
+ GdkDisplay *display;
+ GConfClient *gconf_client;
+ gulong last_idle;
+ guint idle_focus_change_id;
+ guint save_id;
+ guint gconf_notify;
+ gboolean currently_idle;
+ gboolean enable_monitoring;
+
+ GSList *previously_running;
+
+ long watch_start_time;
+ ShellApp *watched_app;
+
+ /* <char *context, GHashTable<char *appid, UsageData *usage>> */
+ GHashTable *app_usages_for_context;
+};
+
+G_DEFINE_TYPE (ShellAppUsage, shell_app_usage, G_TYPE_OBJECT);
+
+/* Represents an application record for a given context */
+struct UsageData
+{
+ /* Whether the application we're tracking is "transient", see
+ * shell_app_info_is_transient.
+ */
+ gboolean transient;
+
+ gdouble score; /* Based on the number of times we'e seen the app and normalized */
+ long last_seen; /* Used to clear old apps we've only seen a few times */
+};
+
+static void shell_app_usage_finalize (GObject *object);
+
+static void on_session_status_changed (DBusGProxy *proxy, guint status, ShellAppUsage *self);
+static void on_focus_app_changed (ShellWindowTracker *tracker, GParamSpec *spec, ShellAppUsage *self);
+static void ensure_queued_save (ShellAppUsage *self);
+static UsageData * get_app_usage_for_context_and_id (ShellAppUsage *self,
+ const char *context,
+ const char *appid);
+
+static gboolean idle_save_application_usage (gpointer data);
+
+static void restore_from_file (ShellAppUsage *self);
+
+static void update_enable_monitoring (ShellAppUsage *self);
+
+static void on_enable_monitoring_key_changed (GConfClient *client,
+ guint connexion_id,
+ GConfEntry *entry,
+ gpointer self);
+
+static long
+get_time (void)
+{
+ GTimeVal tv;
+ g_get_current_time (&tv);
+ return tv.tv_sec;
+}
+
+static void
+shell_app_usage_class_init (ShellAppUsageClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = shell_app_usage_finalize;
+}
+
+static GHashTable *
+get_usages_for_context (ShellAppUsage *self,
+ const char *context)
+{
+ GHashTable *context_usages;
+
+ context_usages = g_hash_table_lookup (self->app_usages_for_context, context);
+ if (context_usages == NULL)
+ {
+ context_usages = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+ g_hash_table_insert (self->app_usages_for_context, g_strdup (context),
+ context_usages);
+ }
+ return context_usages;
+}
+
+static UsageData *
+get_app_usage_for_context_and_id (ShellAppUsage *self,
+ const char *context,
+ const char *appid)
+{
+ UsageData *usage;
+ GHashTable *context_usages;
+
+ context_usages = get_usages_for_context (self, context);
+
+ usage = g_hash_table_lookup (context_usages, appid);
+ if (usage)
+ return usage;
+
+ usage = g_new0 (UsageData, 1);
+ g_hash_table_insert (context_usages, g_strdup (appid), usage);
+
+ return usage;
+}
+
+static UsageData *
+get_usage_for_app (ShellAppUsage *self,
+ ShellApp *app)
+{
+ const char *context;
+
+ context = _shell_window_tracker_get_app_context (shell_window_tracker_get_default (), app);
+
+ return get_app_usage_for_context_and_id (self, context, shell_app_get_id (app));
+}
+
+typedef struct {
+ gboolean in_context;
+ GHashTableIter context_iter;
+ const char *context_id;
+ GHashTableIter usage_iter;
+} UsageIterator;
+
+static void
+usage_iterator_init (ShellAppUsage *self,
+ UsageIterator *iter)
+{
+ iter->in_context = FALSE;
+ g_hash_table_iter_init (&(iter->context_iter), self->app_usages_for_context);
+}
+
+static gboolean
+usage_iterator_next (ShellAppUsage *self,
+ UsageIterator *iter,
+ const char **context,
+ const char **id,
+ UsageData **usage)
+{
+ gpointer key, value;
+ gboolean next_context;
+
+ if (!iter->in_context)
+ next_context = TRUE;
+ else if (!g_hash_table_iter_next (&(iter->usage_iter), &key, &value))
+ next_context = TRUE;
+ else
+ next_context = FALSE;
+
+ while (next_context)
+ {
+ GHashTable *app_usages;
+
+ if (!g_hash_table_iter_next (&(iter->context_iter), &key, &value))
+ return FALSE;
+ iter->in_context = TRUE;
+ iter->context_id = key;
+ app_usages = value;
+ g_hash_table_iter_init (&(iter->usage_iter), app_usages);
+
+ next_context = !g_hash_table_iter_next (&(iter->usage_iter), &key, &value);
+ }
+
+ *context = iter->context_id;
+ *id = key;
+ *usage = value;
+
+ return TRUE;
+}
+
+static void
+usage_iterator_remove (ShellAppUsage *self,
+ UsageIterator *iter)
+{
+ g_assert (iter->in_context);
+
+ g_hash_table_iter_remove (&(iter->usage_iter));
+}
+
+/* Limit the score to a certain level so that most used apps can change */
+static void
+normalize_usage (ShellAppUsage *self)
+{
+ UsageIterator iter;
+ const char *context;
+ const char *id;
+ UsageData *usage;
+
+ usage_iterator_init (self, &iter);
+
+ while (usage_iterator_next (self, &iter, &context, &id, &usage))
+ {
+ usage->score /= 2;
+ }
+}
+
+static void
+increment_usage_for_app_at_time (ShellAppUsage *self,
+ ShellApp *app,
+ long time)
+{
+ UsageData *usage;
+ guint elapsed;
+ guint usage_count;
+
+ usage = get_usage_for_app (self, app);
+
+ usage->last_seen = time;
+
+ elapsed = time - self->watch_start_time;
+ usage_count = elapsed / FOCUS_TIME_MIN_SECONDS;
+ if (usage_count > 0)
+ {
+ usage->score += usage_count;
+ if (usage->score > SCORE_MAX)
+ normalize_usage (self);
+ ensure_queued_save (self);
+ }
+}
+
+static void
+increment_usage_for_app (ShellAppUsage *self,
+ ShellApp *app)
+{
+ long curtime = get_time ();
+ increment_usage_for_app_at_time (self, app, curtime);
+}
+
+static void
+on_app_running_changed (ShellWindowTracker *tracker,
+ ShellApp *app,
+ gpointer user_data)
+{
+ ShellAppUsage *self = SHELL_APP_USAGE (user_data);
+ UsageData *usage;
+ gboolean running;
+
+ if (shell_app_is_transient (app))
+ return;
+
+ usage = get_usage_for_app (self, app);
+
+ running = shell_app_get_n_windows (app) > 0;
+
+ usage->last_seen = get_time ();
+}
+
+static void
+on_focus_app_changed (ShellWindowTracker *tracker,
+ GParamSpec *spec,
+ ShellAppUsage *self)
+{
+ if (self->watched_app != NULL)
+ increment_usage_for_app (self, self->watched_app);
+
+ if (self->watched_app)
+ g_object_unref (self->watched_app);
+
+ g_object_get (tracker, "focus-app", &(self->watched_app), NULL);
+ self->watch_start_time = get_time ();
+}
+
+static void
+on_session_status_changed (DBusGProxy *proxy,
+ guint status,
+ ShellAppUsage *self)
+{
+ gboolean idle;
+
+ idle = (status >= GNOME_SESSION_STATUS_IDLE);
+ if (self->currently_idle == idle)
+ return;
+
+ self->currently_idle = idle;
+ if (idle)
+ {
+ long end_time;
+
+ /* The GNOME Session signal we watch is 5 minutes, but that's a long
+ * time for this purpose. Instead, just add a base 30 seconds.
+ */
+ if (self->watched_app)
+ {
+ end_time = self->watch_start_time + IDLE_TIME_TRANSITION_SECONDS;
+ increment_usage_for_app_at_time (self, self->watched_app, end_time);
+ }
+ }
+ else
+ {
+ /* Transitioning to !idle, reset the start time */
+ self->watch_start_time = get_time ();
+ }
+}
+
+static void
+shell_app_usage_init (ShellAppUsage *self)
+{
+ char *shell_config_dir, *path;
+ DBusGConnection *session_bus;
+ ShellWindowTracker *tracker;
+
+ self->app_usages_for_context = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_hash_table_destroy);
+
+ tracker = shell_window_tracker_get_default ();
+ g_signal_connect (tracker, "notify::focus-app", G_CALLBACK (on_focus_app_changed), self);
+ g_signal_connect (tracker, "app-running-changed", G_CALLBACK (on_app_running_changed), self);
+
+ session_bus = dbus_g_bus_get (DBUS_BUS_SESSION, NULL);
+ self->session_proxy = dbus_g_proxy_new_for_name (session_bus, "org.gnome.SessionManager",
+ "/org/gnome/SessionManager/Presence",
+ "org.gnome.SessionManager");
+ dbus_g_proxy_add_signal (self->session_proxy, "StatusChanged",
+ G_TYPE_UINT, G_TYPE_INVALID, G_TYPE_INVALID);
+ dbus_g_proxy_connect_signal (self->session_proxy, "StatusChanged",
+ G_CALLBACK (on_session_status_changed), self, NULL);
+
+ self->last_idle = 0;
+ self->currently_idle = FALSE;
+ self->enable_monitoring = FALSE;
+
+ g_object_get (shell_global_get(), "configdir", &shell_config_dir, NULL),
+ path = g_build_filename (shell_config_dir, DATA_FILENAME, NULL);
+ g_free (shell_config_dir);
+ self->configfile = g_file_new_for_path (path);
+ g_free (path);
+ restore_from_file (self);
+
+ self->gconf_client = gconf_client_get_default ();
+ gconf_client_add_dir (self->gconf_client, APP_MONITOR_GCONF_DIR,
+ GCONF_CLIENT_PRELOAD_NONE, NULL);
+ self->gconf_notify =
+ gconf_client_notify_add (self->gconf_client, ENABLE_MONITORING_KEY,
+ on_enable_monitoring_key_changed, self, NULL, NULL);
+ update_enable_monitoring (self);
+}
+
+static void
+shell_app_usage_finalize (GObject *object)
+{
+ ShellAppUsage *self = SHELL_APP_USAGE (object);
+
+ if (self->save_id > 0)
+ g_source_remove (self->save_id);
+ gconf_client_notify_remove (self->gconf_client, self->gconf_notify);
+ g_object_unref (self->gconf_client);
+
+ g_object_unref (self->configfile);
+
+ G_OBJECT_CLASS (shell_app_usage_parent_class)->finalize(object);
+}
+
+typedef struct {
+ ShellAppUsage *usage;
+ GHashTable *context_usages;
+} SortAppsByUsageData;
+
+static int
+sort_apps_by_usage (gconstpointer a,
+ gconstpointer b,
+ gpointer datap)
+{
+ SortAppsByUsageData *data = datap;
+ ShellApp *app_a, *app_b;
+ UsageData *usage_a, *usage_b;
+
+ app_a = (ShellApp*)a;
+ app_b = (ShellApp*)b;
+
+ usage_a = g_hash_table_lookup (data->context_usages, shell_app_get_id (app_a));
+ usage_b = g_hash_table_lookup (data->context_usages, shell_app_get_id (app_b));
+
+ return usage_b->score - usage_a->score;
+}
+
+/**
+ * shell_app_usage_get_most_used:
+ * @usage: the usage instance to request
+ * @context: Activity identifier
+ * @max_count: how many applications are requested. Note that the actual
+ * list size may be less, or NULL if not enough applications are registered.
+ *
+ * Get a list of most popular applications for a given context.
+ *
+ * Returns: (element-type ShellApp) (transfer full): List of applications
+ */
+GSList *
+shell_app_usage_get_most_used (ShellAppUsage *self,
+ const char *context,
+ gint max_count)
+{
+ GSList *apps;
+ GList *appids, *iter;
+ GHashTable *usages;
+ ShellAppSystem *appsys;
+ SortAppsByUsageData data;
+
+ usages = g_hash_table_lookup (self->app_usages_for_context, context);
+ if (usages == NULL)
+ return NULL;
+
+ appsys = shell_app_system_get_default ();
+
+ appids = g_hash_table_get_keys (usages);
+ apps = NULL;
+ for (iter = appids; iter; iter = iter->next)
+ {
+ const char *appid = iter->data;
+ ShellApp *app;
+
+ app = shell_app_system_get_app (appsys, appid);
+ if (!app)
+ continue;
+
+ apps = g_slist_prepend (apps, g_object_ref (app));
+ }
+
+ g_list_free (appids);
+
+ data.usage = self;
+ data.context_usages = usages;
+ apps = g_slist_sort_with_data (apps, sort_apps_by_usage, &data);
+
+ return apps;
+}
+
+static void
+ensure_queued_save (ShellAppUsage *self)
+{
+ if (self->save_id != 0)
+ return;
+ self->save_id = g_timeout_add_seconds (SAVE_APPS_TIMEOUT_SECONDS, idle_save_application_usage, self);
+}
+
+/* Used to sort highest scores at the top */
+static gint
+usage_sort_apps (gconstpointer data1,
+ gconstpointer data2)
+{
+ const UsageData *u1 = data1;
+ const UsageData *u2 = data2;
+
+ if (u1->score > u2->score)
+ return -1;
+ else if (u1->score == u2->score)
+ return 0;
+ else
+ return 1;
+}
+
+/* Clean up apps we see rarely.
+ * The logic behind this is that if an app was seen less than SCORE_MIN times
+ * and not seen for a week, it can probably be forgotten about.
+ * This should much reduce the size of the list and avoid 'pollution'. */
+static gboolean
+idle_clean_usage (ShellAppUsage *self)
+{
+ UsageIterator iter;
+ const char *context;
+ const char *id;
+ UsageData *usage;
+ long current_time;
+ long week_ago;
+
+ current_time = get_time ();
+ week_ago = current_time - (7 * 24 * 60 * 60);
+
+ usage_iterator_init (self, &iter);
+
+ while (usage_iterator_next (self, &iter, &context, &id, &usage))
+ {
+ if ((usage->score < SCORE_MIN) &&
+ (usage->last_seen < week_ago))
+ usage_iterator_remove (self, &iter);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+write_escaped (GDataOutputStream *stream,
+ const char *str,
+ GError **error)
+{
+ gboolean ret;
+ char *quoted = g_markup_escape_text (str, -1);
+ ret = g_data_output_stream_put_string (stream, quoted, NULL, error);
+ g_free (quoted);
+ return ret;
+}
+
+static gboolean
+write_attribute_string (GDataOutputStream *stream,
+ const char *elt_name,
+ const char *str,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ char *elt;
+
+ elt = g_strdup_printf (" %s=\"", elt_name);
+ ret = g_data_output_stream_put_string (stream, elt, NULL, error);
+ g_free (elt);
+ if (!ret)
+ goto out;
+
+ ret = write_escaped (stream, str, error);
+ if (!ret)
+ goto out;
+
+ ret = g_data_output_stream_put_string (stream, "\"", NULL, error);
+
+out:
+ return ret;
+}
+
+static gboolean
+write_attribute_uint (GDataOutputStream *stream,
+ const char *elt_name,
+ guint value,
+ GError **error)
+{
+ gboolean ret;
+ char *buf;
+
+ buf = g_strdup_printf ("%u", value);
+ ret = write_attribute_string (stream, elt_name, buf, error);
+ g_free (buf);
+
+ return ret;
+}
+
+static gboolean
+write_attribute_double (GDataOutputStream *stream,
+ const char *elt_name,
+ double value,
+ GError **error)
+{
+ gchar buf[G_ASCII_DTOSTR_BUF_SIZE];
+ gboolean ret;
+
+ g_ascii_dtostr (buf, sizeof (buf), value);
+ ret = write_attribute_string (stream, elt_name, buf, error);
+
+ return ret;
+}
+
+/* Save app data lists to file */
+static gboolean
+idle_save_application_usage (gpointer data)
+{
+ ShellAppUsage *self = SHELL_APP_USAGE (data);
+ UsageIterator iter;
+ const char *current_context;
+ const char *context;
+ const char *id;
+ UsageData *usage;
+ GFileOutputStream *output;
+ GOutputStream *buffered_output;
+ GDataOutputStream *data_output;
+ GError *error = NULL;
+
+ self->save_id = 0;
+
+ /* Parent directory is already created by shell-global */
+ output = g_file_replace (self->configfile, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error);
+ if (!output)
+ {
+ g_debug ("Could not save applications usage data: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+ buffered_output = g_buffered_output_stream_new (G_OUTPUT_STREAM (output));
+ g_object_unref (output);
+ data_output = g_data_output_stream_new (G_OUTPUT_STREAM (buffered_output));
+ g_object_unref (buffered_output);
+
+ if (!g_data_output_stream_put_string (data_output, "<?xml version=\"1.0\"?>\n<application-state>\n", NULL, &error))
+ goto out;
+
+ usage_iterator_init (self, &iter);
+
+ current_context = NULL;
+ while (usage_iterator_next (self, &iter, &context, &id, &usage))
+ {
+ ShellApp *app;
+
+ app = shell_app_system_get_app (shell_app_system_get_default(), id);
+
+ if (!app)
+ continue;
+
+ if (context != current_context)
+ {
+ if (current_context != NULL)
+ {
+ if (!g_data_output_stream_put_string (data_output, " </context>", NULL, &error))
+ goto out;
+ }
+ current_context = context;
+ if (!g_data_output_stream_put_string (data_output, " <context", NULL, &error))
+ goto out;
+ if (!write_attribute_string (data_output, "id", context, &error))
+ goto out;
+ if (!g_data_output_stream_put_string (data_output, ">\n", NULL, &error))
+ goto out;
+ }
+ if (!g_data_output_stream_put_string (data_output, " <application", NULL, &error))
+ goto out;
+ if (!write_attribute_string (data_output, "id", id, &error))
+ goto out;
+ if (!write_attribute_uint (data_output, "open-window-count", shell_app_get_n_windows (app), &error))
+ goto out;
+
+ if (!write_attribute_double (data_output, "score", usage->score, &error))
+ goto out;
+ if (!write_attribute_uint (data_output, "last-seen", usage->last_seen, &error))
+ goto out;
+ if (!g_data_output_stream_put_string (data_output, "/>\n", NULL, &error))
+ goto out;
+ }
+ if (current_context != NULL)
+ {
+ if (!g_data_output_stream_put_string (data_output, " </context>\n", NULL, &error))
+ goto out;
+ }
+ if (!g_data_output_stream_put_string (data_output, "</application-state>\n", NULL, &error))
+ goto out;
+
+out:
+ if (!error)
+ g_output_stream_close (G_OUTPUT_STREAM(data_output), NULL, &error);
+ g_object_unref (data_output);
+ if (error)
+ {
+ g_debug ("Could not save applications usage data: %s", error->message);
+ g_error_free (error);
+ }
+ return FALSE;
+}
+
+typedef struct {
+ ShellAppUsage *self;
+ char *context;
+} ParseData;
+
+static void
+shell_app_usage_start_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ ParseData *data = user_data;
+
+ if (strcmp (element_name, "application-state") == 0)
+ {
+ }
+ else if (strcmp (element_name, "context") == 0)
+ {
+ char *context = NULL;
+ const char **attribute;
+ const char **value;
+
+ for (attribute = attribute_names, value = attribute_values; *attribute; attribute++, value++)
+ {
+ if (strcmp (*attribute, "id") == 0)
+ context = g_strdup (*value);
+ }
+ if (context < 0)
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ "Missing attribute id on <%s> element",
+ element_name);
+ return;
+ }
+ data->context = context;
+ }
+ else if (strcmp (element_name, "application") == 0)
+ {
+ const char **attribute;
+ const char **value;
+ UsageData *usage;
+ char *appid = NULL;
+ GHashTable *usage_table;
+
+ for (attribute = attribute_names, value = attribute_values; *attribute; attribute++, value++)
+ {
+ if (strcmp (*attribute, "id") == 0)
+ appid = g_strdup (*value);
+ }
+
+ if (!appid)
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ "Missing attribute id on <%s> element",
+ element_name);
+ return;
+ }
+
+ usage_table = get_usages_for_context (data->self, data->context);
+
+ usage = g_new0 (UsageData, 1);
+ g_hash_table_insert (usage_table, appid, usage);
+
+ for (attribute = attribute_names, value = attribute_values; *attribute; attribute++, value++)
+ {
+ if (strcmp (*attribute, "open-window-count") == 0)
+ {
+ guint count = strtoul (*value, NULL, 10);
+ if (count > 0)
+ data->self->previously_running = g_slist_prepend (data->self->previously_running,
+ usage);
+ }
+ else if (strcmp (*attribute, "score") == 0)
+ {
+ usage->score = g_ascii_strtod (*value, NULL);
+ }
+ else if (strcmp (*attribute, "last-seen") == 0)
+ {
+ usage->last_seen = (guint) g_ascii_strtoull (*value, NULL, 10);
+ }
+ }
+ }
+ else
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ "Unknown element <%s>",
+ element_name);
+ }
+}
+
+static void
+shell_app_usage_end_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ ParseData *data = user_data;
+
+ if (strcmp (element_name, "context") == 0)
+ {
+ g_free (data->context);
+ data->context = NULL;
+ }
+}
+
+static void
+shell_app_usage_text_handler (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ /* do nothing, very very fast */
+}
+
+static GMarkupParser app_state_parse_funcs =
+{
+ shell_app_usage_start_element_handler,
+ shell_app_usage_end_element_handler,
+ shell_app_usage_text_handler,
+ NULL,
+ NULL
+};
+
+/* Load data about apps usage from file */
+static void
+restore_from_file (ShellAppUsage *self)
+{
+ GFileInputStream *input;
+ ParseData parse_data;
+ GMarkupParseContext *parse_context;
+ GError *error = NULL;
+ char buf[1024];
+
+ input = g_file_read (self->configfile, NULL, &error);
+ if (error)
+ {
+ if (error->code != G_IO_ERROR_NOT_FOUND)
+ g_warning ("Could not load applications usage data: %s", error->message);
+
+ g_error_free (error);
+ return;
+ }
+
+ memset (&parse_data, 0, sizeof (ParseData));
+ parse_data.self = self;
+ parse_data.context = NULL;
+ parse_context = g_markup_parse_context_new (&app_state_parse_funcs, 0, &parse_data, NULL);
+
+ while (TRUE)
+ {
+ gssize count = g_input_stream_read ((GInputStream*) input, buf, sizeof(buf), NULL, &error);
+ if (count <= 0)
+ goto out;
+ if (!g_markup_parse_context_parse (parse_context, buf, count, &error))
+ goto out;
+ }
+
+out:
+ g_free (parse_data.context);
+ g_markup_parse_context_free (parse_context);
+ g_input_stream_close ((GInputStream*)input, NULL, NULL);
+ g_object_unref (input);
+
+ idle_clean_usage (self);
+ self->previously_running = g_slist_sort (self->previously_running, usage_sort_apps);
+
+ if (error)
+ {
+ g_warning ("Could not load applications usage data: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+/* Enable or disable the timers, depending on the value of ENABLE_MONITORING_KEY
+ * and taking care of the previous state. If selfing is disabled, we still
+ * report apps usage based on (possibly) saved data, but don't collect data.
+ */
+static void
+update_enable_monitoring (ShellAppUsage *self)
+{
+ GConfValue *value;
+ gboolean enable;
+
+ value = gconf_client_get (self->gconf_client, ENABLE_MONITORING_KEY, NULL);
+ if (value)
+ {
+ enable = gconf_value_get_bool (value);
+ gconf_value_free (value);
+ }
+ else /* Schema is not present, set default value by hand to avoid getting FALSE */
+ enable = TRUE;
+
+ /* Be sure not to start the timers if they were already set */
+ if (enable && !self->enable_monitoring)
+ {
+ on_focus_app_changed (shell_window_tracker_get_default (), NULL, self);
+ }
+ /* ...and don't try to stop them if they were not running */
+ else if (!enable && self->enable_monitoring)
+ {
+ if (self->watched_app)
+ g_object_unref (self->watched_app);
+ self->watched_app = NULL;
+ if (self->save_id)
+ {
+ g_source_remove (self->save_id);
+ self->save_id = 0;
+ }
+ }
+
+ self->enable_monitoring = enable;
+}
+
+/* Called when the ENABLE_MONITORING_KEY boolean has changed */
+static void
+on_enable_monitoring_key_changed (GConfClient *client,
+ guint connexion_id,
+ GConfEntry *entry,
+ gpointer self)
+{
+ update_enable_monitoring ((ShellAppUsage *) self);
+}
+
+/**
+ * shell_app_usage_get_default:
+ *
+ * Return Value: (transfer none): The global #ShellAppUsage instance
+ */
+ShellAppUsage *
+shell_app_usage_get_default ()
+{
+ static ShellAppUsage *instance;
+
+ if (instance == NULL)
+ instance = g_object_new (SHELL_TYPE_APP_USAGE, NULL);
+
+ return instance;
+}
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_APP_USAGE_H__
+#define __SHELL_APP_USAGE_H__
+
+#include "shell-app.h"
+#include "shell-window-tracker.h"
+
+G_BEGIN_DECLS
+
+typedef struct _ShellAppUsage ShellAppUsage;
+typedef struct _ShellAppUsageClass ShellAppUsageClass;
+typedef struct _ShellAppUsagePrivate ShellAppUsagePrivate;
+
+#define SHELL_TYPE_APP_USAGE (shell_app_usage_get_type ())
+#define SHELL_APP_USAGE(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SHELL_TYPE_APP_USAGE, ShellAppUsage))
+#define SHELL_APP_USAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_APP_USAGE, ShellAppUsageClass))
+#define SHELL_IS_APP_USAGE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SHELL_TYPE_APP_USAGE))
+#define SHELL_IS_APP_USAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_APP_USAGE))
+#define SHELL_APP_USAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_APP_USAGE, ShellAppUsageClass))
+
+struct _ShellAppUsageClass
+{
+ GObjectClass parent_class;
+};
+
+GType shell_app_usage_get_type (void) G_GNUC_CONST;
+
+ShellAppUsage* shell_app_usage_get_default(void);
+
+GSList *shell_app_usage_get_most_used (ShellAppUsage *monitor,
+ const char *context,
+ gint number);
+
+G_END_DECLS
+
+#endif /* __SHELL_APP_USAGE_H__ */
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
-#include "shell-app.h"
+#include "shell-app-private.h"
#include "shell-global.h"
/**
return shell_app_info_is_transient (app->info);
}
+gboolean
+shell_app_launch (ShellApp *self,
+ GError **error)
+{
+ return shell_app_info_launch (self->info, error);
+}
+
/**
- * shell_app_get_info:
+ * _shell_app_get_info:
*
* Returns: (transfer none): Associated app info
*/
ShellAppInfo *
-shell_app_get_info (ShellApp *app)
+_shell_app_get_info (ShellApp *app)
{
return app->info;
}
return app->windows;
}
+guint
+shell_app_get_n_windows (ShellApp *app)
+{
+ return g_slist_length (app->windows);
+}
+
static gboolean
shell_app_has_visible_windows (ShellApp *app)
{
app = g_object_new (SHELL_TYPE_APP, NULL);
app->info = shell_app_system_create_from_window (shell_app_system_get_default (), window);
+ _shell_app_system_register_app (shell_app_system_get_default (), app);
_shell_app_add_window (app, window);
return app;
app = g_object_new (SHELL_TYPE_APP, NULL);
app->info = shell_app_info_ref (info);
+ _shell_app_system_register_app (shell_app_system_get_default (), app);
return app;
}
#ifndef __SHELL_APP_H__
#define __SHELL_APP_H__
-#include <glib-object.h>
-#include <glib.h>
+#include <clutter/clutter.h>
#include "window.h"
-#include "shell-app-system.h"
G_BEGIN_DECLS
char *shell_app_get_name (ShellApp *app);
char *shell_app_get_description (ShellApp *app);
gboolean shell_app_is_transient (ShellApp *app);
+gboolean shell_app_launch (ShellApp *info, GError **error);
-ShellAppInfo *shell_app_get_info (ShellApp *app);
+guint shell_app_get_n_windows (ShellApp *app);
GSList *shell_app_get_windows (ShellApp *app);
int shell_app_compare (ShellApp *app, ShellApp *other);
-ShellApp* _shell_app_new_for_window (MetaWindow *window);
-
-ShellApp* _shell_app_new (ShellAppInfo *appinfo);
-
-void _shell_app_add_window (ShellApp *app, MetaWindow *window);
-
-void _shell_app_remove_window (ShellApp *app, MetaWindow *window);
-
G_END_DECLS
#endif /* __SHELL_APP_H__ */
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#include <string.h>
+#include <stdlib.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+
+#define SN_API_NOT_YET_FROZEN 1
+#include <libsn/sn.h>
+
+#include "shell-window-tracker.h"
+#include "shell-app-system.h"
+#include "shell-app-private.h"
+#include "shell-texture-cache.h"
+#include "shell-global.h"
+#include "shell-marshal.h"
+
+#include "display.h"
+#include "window.h"
+#include "group.h"
+#include "util.h"
+
+/* This file includes modified code from
+ * desktop-data-engine/engine-dbus/hippo-application-monitor.c
+ * in the functions collecting application usage data.
+ * Written by Owen Taylor, originally licensed under LGPL 2.1.
+ * Copyright Red Hat, Inc. 2006-2008
+ */
+
+/**
+ * SECTION:shell-window-tracker
+ * @short_description: Associate windows with applications
+ *
+ * Maintains a mapping from windows to applications (.desktop file ids).
+ * It currently implements this with some heuristics on the WM_CLASS X11
+ * property (and some static override regexps); in the future, we want to
+ * have it also track through startup-notification.
+ */
+
+/* Title patterns to detect apps that don't set WM class as needed.
+ * Format: application ID, title regex pattern, NULL (for GRegex) */
+static struct
+{
+ const char *app_id;
+ const char *pattern;
+ GRegex *regex;
+} title_patterns[] = {
+ {"mozilla-firefox.desktop", ".* - Mozilla Firefox", NULL}, \
+ {"openoffice.org-writer.desktop", ".* - OpenOffice.org Writer$", NULL}, \
+ {"openoffice.org-calc.desktop", ".* - OpenOffice.org Calc$", NULL}, \
+ {"openoffice.org-impress.desktop", ".* - OpenOffice.org Impress$", NULL}, \
+ {"openoffice.org-draw.desktop", ".* - OpenOffice.org Draw$", NULL}, \
+ {"openoffice.org-base.desktop", ".* - OpenOffice.org Base$", NULL}, \
+ {"openoffice.org-math.desktop", ".* - OpenOffice.org Math$", NULL}, \
+ {NULL, NULL, NULL}
+};
+
+struct _ShellWindowTracker
+{
+ GObject parent;
+
+ guint idle_focus_change_id;
+ ShellApp *focus_app;
+
+ /* <MetaWindow * window, ShellApp *app> */
+ GHashTable *window_to_app;
+
+ /* <const char *id, ShellApp *app> */
+ GHashTable *running_apps;
+};
+
+G_DEFINE_TYPE (ShellWindowTracker, shell_window_tracker, G_TYPE_OBJECT);
+
+enum {
+ PROP_0,
+ PROP_FOCUS_APP
+};
+
+enum {
+ APP_RUNNING_CHANGED,
+ STARTUP_SEQUENCE_CHANGED,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void shell_window_tracker_finalize (GObject *object);
+
+static void on_focus_window_changed (MetaDisplay *display, GParamSpec *spec, ShellWindowTracker *tracker);
+
+static void track_window (ShellWindowTracker *monitor, MetaWindow *window);
+static void disassociate_window (ShellWindowTracker *monitor, MetaWindow *window);
+
+
+static void
+shell_window_tracker_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ShellWindowTracker *tracker = SHELL_WINDOW_TRACKER (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_FOCUS_APP:
+ g_value_set_object (value, tracker->focus_app);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+shell_window_tracker_class_init (ShellWindowTrackerClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->get_property = shell_window_tracker_get_property;
+ gobject_class->finalize = shell_window_tracker_finalize;
+
+ g_object_class_install_property (gobject_class,
+ PROP_FOCUS_APP,
+ g_param_spec_object ("focus-app",
+ "Focus App",
+ "Focused application",
+ SHELL_TYPE_APP,
+ G_PARAM_READABLE));
+
+ signals[APP_RUNNING_CHANGED] = g_signal_new ("app-running-changed",
+ SHELL_TYPE_WINDOW_TRACKER,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ SHELL_TYPE_APP);
+ signals[STARTUP_SEQUENCE_CHANGED] = g_signal_new ("startup-sequence-changed",
+ SHELL_TYPE_WINDOW_TRACKER,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOXED,
+ G_TYPE_NONE, 1, SHELL_TYPE_STARTUP_SEQUENCE);
+}
+
+/**
+ * get_app_id_from_title:
+ *
+ * Use a window's "title" property to determine an application ID.
+ * This is a temporary crutch for a few applications until we get
+ * them correctly setting their WM_CLASS.
+ */
+static const char *
+get_app_id_from_title (MetaWindow *window)
+{
+ static gboolean patterns_initialized = FALSE;
+ const char *title;
+ int i;
+
+ title = meta_window_get_title (window);
+
+ if (!patterns_initialized) /* Generate match patterns once for all */
+ {
+ patterns_initialized = TRUE;
+ for (i = 0; title_patterns[i].app_id; i++)
+ {
+ title_patterns[i].regex = g_regex_new (title_patterns[i].pattern,
+ 0, 0, NULL);
+ }
+ }
+
+ /* Match window title patterns to identifiers for non-standard apps */
+ if (title)
+ {
+ for (i = 0; title_patterns[i].app_id; i++)
+ {
+ if (g_regex_match (title_patterns[i].regex, title, 0, NULL))
+ {
+ /* Matched, return the app id we want */
+ return title_patterns[i].app_id;
+ }
+ }
+ }
+ return NULL;
+}
+
+/**
+ * get_appid_from_window:
+ *
+ * Turn the WM_CLASS property into our best guess at a .desktop file id.
+ */
+static char *
+get_appid_from_window (MetaWindow *window)
+{
+ const char *wmclass;
+ char *appid_guess;
+
+ wmclass = meta_window_get_wm_class (window);
+ if (!wmclass)
+ return NULL;
+
+ appid_guess = g_ascii_strdown (wmclass, -1);
+
+ /* This handles "Fedora Eclipse", probably others.
+ * Note g_strdelimit is modify-in-place. */
+ g_strdelimit (appid_guess, " ", '-');
+
+ return appid_guess;
+}
+
+/**
+ * window_is_tracked:
+ *
+ * We don't attempt to associate override-redirect windows with applications
+ * at all, since there's no reason to do so yet.
+ *
+ * Returns: %TRUE iff we want to scan this window for application association
+ */
+static gboolean
+window_is_tracked (MetaWindow *window)
+{
+ if (meta_window_is_override_redirect (window))
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * shell_window_tracker_is_window_interesting:
+ *
+ * The ShellWindowTracker associates certain kinds of windows with
+ * applications; however, others we don't want to
+ * appear in places where we want to give a list of windows
+ * for an application, such as the alt-tab dialog.
+ *
+ * An example of a window we don't want to show is the root
+ * desktop window. We skip all override-redirect types, and also
+ * exclude other window types like tooltip explicitly, though generally
+ * most of these should be override-redirect.
+ *
+ * Returns: %TRUE iff a window is "interesting"
+ */
+gboolean
+shell_window_tracker_is_window_interesting (MetaWindow *window)
+{
+ if (!window_is_tracked (window))
+ return FALSE;
+
+ if (meta_window_is_skip_taskbar (window))
+ return FALSE;
+
+ switch (meta_window_get_window_type (window))
+ {
+ /* Definitely ignore these. */
+ case META_WINDOW_DESKTOP:
+ case META_WINDOW_DOCK:
+ case META_WINDOW_SPLASHSCREEN:
+ /* Should have already been handled by override_redirect above,
+ * but explicitly list here so we get the "unhandled enum"
+ * warning if in the future anything is added.*/
+ case META_WINDOW_DROPDOWN_MENU:
+ case META_WINDOW_POPUP_MENU:
+ case META_WINDOW_TOOLTIP:
+ case META_WINDOW_NOTIFICATION:
+ case META_WINDOW_COMBO:
+ case META_WINDOW_DND:
+ case META_WINDOW_OVERRIDE_OTHER:
+ return FALSE;
+ case META_WINDOW_NORMAL:
+ case META_WINDOW_DIALOG:
+ case META_WINDOW_MODAL_DIALOG:
+ case META_WINDOW_MENU:
+ case META_WINDOW_TOOLBAR:
+ case META_WINDOW_UTILITY:
+ break;
+ }
+
+ return TRUE;
+}
+
+/**
+ * get_app_for_window_direct:
+ *
+ * Looks only at the given window, and attempts to determine
+ * an application based on WM_CLASS. If that fails, then
+ * a "transient" application is created.
+ *
+ * Return value: (transfer full): A newly-referenced #ShellApp
+ */
+static ShellApp *
+get_app_for_window_direct (MetaWindow *window)
+{
+ ShellApp *app;
+ ShellAppSystem *appsys;
+ char *wmclass;
+ char *with_desktop;
+
+ wmclass = get_appid_from_window (window);
+
+ if (!wmclass)
+ return _shell_app_new_for_window (window);
+
+ with_desktop = g_strjoin (NULL, wmclass, ".desktop", NULL);
+ g_free (wmclass);
+
+ appsys = shell_app_system_get_default ();
+ app = shell_app_system_lookup_heuristic_basename (appsys, with_desktop);
+ g_free (with_desktop);
+
+ if (app == NULL)
+ {
+ const char *id = get_app_id_from_title (window);
+
+ if (id != NULL)
+ app = shell_app_system_get_app (appsys, id);
+ }
+
+ return app;
+}
+
+/**
+ * get_app_for_window:
+ *
+ * Determines the application associated with a window, using
+ * all available information such as the window's MetaGroup,
+ * and what we know about other windows.
+ */
+static ShellApp *
+get_app_for_window (ShellWindowTracker *monitor,
+ MetaWindow *window)
+{
+ ShellApp *result;
+ MetaWindow *source_window;
+ GSList *group_windows;
+ MetaGroup *group;
+ GSList *iter;
+
+ group = meta_window_get_group (window);
+ if (group == NULL)
+ group_windows = g_slist_prepend (NULL, window);
+ else
+ group_windows = meta_group_list_windows (group);
+
+ source_window = window;
+
+ result = NULL;
+ /* Try finding a window in the group of type NORMAL; if we
+ * succeed, use that as our source. */
+ for (iter = group_windows; iter; iter = iter->next)
+ {
+ MetaWindow *group_window = iter->data;
+
+ if (meta_window_get_window_type (group_window) != META_WINDOW_NORMAL)
+ continue;
+
+ source_window = group_window;
+ result = g_hash_table_lookup (monitor->window_to_app, group_window);
+ if (result)
+ break;
+ }
+
+ g_slist_free (group_windows);
+
+ if (result != NULL)
+ {
+ g_object_ref (result);
+ return result;
+ }
+
+ return get_app_for_window_direct (source_window);
+}
+
+const char *
+_shell_window_tracker_get_app_context (ShellWindowTracker *tracker, ShellApp *app)
+{
+ return "";
+}
+
+static MetaWindow *
+get_active_window (ShellWindowTracker *monitor)
+{
+ MetaScreen *screen;
+ MetaDisplay *display;
+ MetaWindow *window;
+
+ screen = shell_global_get_screen (shell_global_get ());
+ display = meta_screen_get_display (screen);
+ window = meta_display_get_focus_window (display);
+
+ if (window != NULL && shell_window_tracker_is_window_interesting (window))
+ return window;
+ return NULL;
+}
+
+static void
+on_transient_window_title_changed (MetaWindow *window,
+ GParamSpec *spec,
+ ShellWindowTracker *self)
+{
+ ShellAppSystem *appsys;
+ ShellApp *app;
+ const char *id;
+
+ /* Check if we now have a mapping using the window title */
+ id = get_app_id_from_title (window);
+ if (id == NULL)
+ return;
+
+ appsys = shell_app_system_get_default ();
+ app = shell_app_system_get_app (appsys, id);
+ if (app == NULL)
+ return;
+ g_object_unref (app);
+
+ /* We found an app, don't listen for further title changes */
+ g_signal_handlers_disconnect_by_func (window, G_CALLBACK (on_transient_window_title_changed),
+ self);
+
+ /* It's simplest to just treat this as a remove + add. */
+ disassociate_window (self, window);
+ track_window (self, window);
+}
+
+static void
+track_window (ShellWindowTracker *self,
+ MetaWindow *window)
+{
+ ShellApp *app;
+
+ if (!window_is_tracked (window))
+ return;
+
+ app = get_app_for_window (self, window);
+ if (!app)
+ return;
+
+ /* At this point we've stored the association from window -> application */
+ g_hash_table_insert (self->window_to_app, window, app);
+
+ /* However, only put interesting windows in the window list for an app. */
+ if (!shell_window_tracker_is_window_interesting (window))
+ return;
+
+ if (shell_app_is_transient (app))
+ {
+ /* For a transient application, it's possible one of our title regexps
+ * will match at a later time, i.e. the application may not have set
+ * its title fully at the time it initially maps a window. Watch
+ * for title changes and recompute the app.
+ */
+ g_signal_connect (window, "notify::title", G_CALLBACK (on_transient_window_title_changed), self);
+ }
+
+ _shell_app_add_window (app, window);
+
+ if (shell_app_get_n_windows (app) == 1)
+ {
+ /* key is owned by the app */
+ g_hash_table_insert (self->running_apps, (char*)shell_app_get_id (app),
+ app);
+ g_signal_emit (self, signals[APP_RUNNING_CHANGED], 0, app);
+ }
+}
+
+static void
+shell_window_tracker_on_window_added (MetaWorkspace *workspace,
+ MetaWindow *window,
+ gpointer user_data)
+{
+ ShellWindowTracker *self = SHELL_WINDOW_TRACKER (user_data);
+
+ track_window (self, window);
+}
+
+static void
+disassociate_window (ShellWindowTracker *self,
+ MetaWindow *window)
+{
+ ShellApp *app;
+
+ app = g_hash_table_lookup (self->window_to_app, window);
+ if (!app)
+ return;
+
+ g_object_ref (app);
+
+ g_hash_table_remove (self->window_to_app, window);
+
+ _shell_app_remove_window (app, window);
+
+ if (shell_app_get_n_windows (app) == 0)
+ {
+ const char *id = shell_app_get_id (app);
+ g_hash_table_remove (self->running_apps, id);
+ g_signal_emit (self, signals[APP_RUNNING_CHANGED], 0, app);
+ }
+
+ g_object_unref (app);
+}
+
+static void
+shell_window_tracker_on_window_removed (MetaWorkspace *workspace,
+ MetaWindow *window,
+ gpointer user_data)
+{
+ disassociate_window (SHELL_WINDOW_TRACKER (user_data), window);
+}
+
+static void
+load_initial_windows (ShellWindowTracker *monitor)
+{
+ GList *workspaces, *iter;
+ MetaScreen *screen = shell_global_get_screen (shell_global_get ());
+ workspaces = meta_screen_get_workspaces (screen);
+
+ for (iter = workspaces; iter; iter = iter->next)
+ {
+ MetaWorkspace *workspace = iter->data;
+ GList *windows = meta_workspace_list_windows (workspace);
+ GList *window_iter;
+
+ for (window_iter = windows; window_iter; window_iter = window_iter->next)
+ {
+ MetaWindow *window = window_iter->data;
+ track_window (monitor, window);
+ }
+
+ g_list_free (windows);
+ }
+}
+
+static void
+shell_window_tracker_on_n_workspaces_changed (MetaScreen *screen,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ ShellWindowTracker *self = SHELL_WINDOW_TRACKER (user_data);
+ GList *workspaces, *iter;
+
+ workspaces = meta_screen_get_workspaces (screen);
+
+ for (iter = workspaces; iter; iter = iter->next)
+ {
+ MetaWorkspace *workspace = iter->data;
+
+ /* This pair of disconnect/connect is idempotent if we were
+ * already connected, while ensuring we get connected for
+ * new workspaces.
+ */
+ g_signal_handlers_disconnect_by_func (workspace,
+ shell_window_tracker_on_window_added,
+ self);
+ g_signal_handlers_disconnect_by_func (workspace,
+ shell_window_tracker_on_window_removed,
+ self);
+
+ g_signal_connect (workspace, "window-added",
+ G_CALLBACK (shell_window_tracker_on_window_added), self);
+ g_signal_connect (workspace, "window-removed",
+ G_CALLBACK (shell_window_tracker_on_window_removed), self);
+ }
+}
+
+static void
+init_window_tracking (ShellWindowTracker *self)
+{
+ MetaDisplay *display;
+ MetaScreen *screen = shell_global_get_screen (shell_global_get ());
+
+ g_signal_connect (screen, "notify::n-workspaces",
+ G_CALLBACK (shell_window_tracker_on_n_workspaces_changed), self);
+ display = meta_screen_get_display (screen);
+ g_signal_connect (display, "notify::focus-window",
+ G_CALLBACK (on_focus_window_changed), self);
+
+ shell_window_tracker_on_n_workspaces_changed (screen, NULL, self);
+}
+
+static void
+on_startup_sequence_changed (MetaScreen *screen,
+ SnStartupSequence *sequence,
+ ShellWindowTracker *self)
+{
+ /* Just proxy the signal */
+ g_signal_emit (G_OBJECT (self), signals[STARTUP_SEQUENCE_CHANGED], 0, sequence);
+}
+
+static void
+shell_window_tracker_init (ShellWindowTracker *self)
+{
+ MetaScreen *screen;
+
+ self->window_to_app = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify) g_object_unref);
+
+ self->running_apps = g_hash_table_new (g_str_hash, g_str_equal);
+
+ screen = shell_global_get_screen (shell_global_get ());
+
+ g_signal_connect (G_OBJECT (screen), "startup-sequence-changed",
+ G_CALLBACK (on_startup_sequence_changed), self);
+
+ load_initial_windows (self);
+ init_window_tracking (self);
+}
+
+static void
+shell_window_tracker_finalize (GObject *object)
+{
+ ShellWindowTracker *self = SHELL_WINDOW_TRACKER (object);
+ int i;
+
+ g_hash_table_destroy (self->running_apps);
+ g_hash_table_destroy (self->window_to_app);
+ for (i = 0; title_patterns[i].app_id; i++)
+ g_regex_unref (title_patterns[i].regex);
+
+ G_OBJECT_CLASS (shell_window_tracker_parent_class)->finalize(object);
+}
+
+/**
+ * shell_window_tracker_get_window_app
+ * @monitor: An app monitor instance
+ * @metawin: A #MetaWindow
+ *
+ * Returns: (transfer full): Application associated with window
+ */
+ShellApp *
+shell_window_tracker_get_window_app (ShellWindowTracker *monitor,
+ MetaWindow *metawin)
+{
+ MetaWindow *transient_for;
+ ShellApp *app;
+
+ transient_for = meta_window_get_transient_for (metawin);
+ if (transient_for != NULL)
+ metawin = transient_for;
+
+ app = g_hash_table_lookup (monitor->window_to_app, metawin);
+ if (app)
+ g_object_ref (app);
+
+ return app;
+}
+
+/**
+ * shell_window_tracker_get_running_apps:
+ * @monitor: An app monitor instance
+ * @context: Activity identifier
+ *
+ * Returns the set of applications which currently have at least one open
+ * window in the given context. The returned list will be sorted
+ * by shell_app_compare().
+ *
+ * Returns: (element-type ShellApp) (transfer full): Active applications
+ */
+GSList *
+shell_window_tracker_get_running_apps (ShellWindowTracker *monitor,
+ const char *context)
+{
+ gpointer key, value;
+ GSList *ret;
+ GHashTableIter iter;
+
+ g_hash_table_iter_init (&iter, monitor->running_apps);
+
+ ret = NULL;
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ ShellApp *app = value;
+
+ if (strcmp (context, _shell_window_tracker_get_app_context (monitor, app)) != 0)
+ continue;
+
+ ret = g_slist_prepend (ret, g_object_ref (app));
+ }
+
+ ret = g_slist_sort (ret, (GCompareFunc)shell_app_compare);
+
+ return ret;
+}
+
+static gboolean
+idle_handle_focus_change (gpointer data)
+{
+ ShellWindowTracker *tracker = data;
+ MetaWindow *new_focus_win;
+ ShellApp *new_focus_app;
+
+ tracker->idle_focus_change_id = 0;
+
+ new_focus_win = get_active_window (tracker);
+ new_focus_app = new_focus_win ? g_hash_table_lookup (tracker->window_to_app, new_focus_win) : NULL;
+
+ if (new_focus_app == tracker->focus_app)
+ return FALSE;
+
+ if (tracker->focus_app != NULL)
+ g_object_unref (tracker->focus_app);
+
+ if (tracker->focus_app != NULL
+ && (!new_focus_win || !new_focus_app))
+ tracker->focus_app = NULL;
+ else
+ tracker->focus_app = g_object_ref (new_focus_app);
+
+ g_object_notify (G_OBJECT (tracker), "focus-app");
+
+ return FALSE;
+}
+
+static void
+on_focus_window_changed (MetaDisplay *display,
+ GParamSpec *spec,
+ ShellWindowTracker *self)
+{
+ if (self->idle_focus_change_id != 0)
+ return;
+
+ self->idle_focus_change_id = meta_later_add (META_LATER_BEFORE_REDRAW, idle_handle_focus_change, self, NULL);
+}
+
+
+/**
+ * shell_window_tracker_get_startup_sequences:
+ * @self:
+ *
+ * Returns: (transfer none) (element-type ShellStartupSequence): Currently active startup sequences
+ */
+GSList *
+shell_window_tracker_get_startup_sequences (ShellWindowTracker *self)
+{
+ ShellGlobal *global = shell_global_get ();
+ MetaScreen *screen = shell_global_get_screen (global);
+ return meta_screen_get_startup_sequences (screen);
+}
+
+/* sn_startup_sequence_ref returns void, so make a
+ * wrapper which returns self */
+static SnStartupSequence *
+sequence_ref (SnStartupSequence *sequence)
+{
+ sn_startup_sequence_ref (sequence);
+ return sequence;
+}
+
+GType
+shell_startup_sequence_get_type (void)
+{
+ static GType gtype = G_TYPE_INVALID;
+ if (gtype == G_TYPE_INVALID)
+ {
+ gtype = g_boxed_type_register_static ("ShellStartupSequence",
+ (GBoxedCopyFunc)sequence_ref,
+ (GBoxedFreeFunc)sn_startup_sequence_unref);
+ }
+ return gtype;
+}
+
+const char *
+shell_startup_sequence_get_id (ShellStartupSequence *sequence)
+{
+ return sn_startup_sequence_get_id ((SnStartupSequence*)sequence);
+}
+
+const char *
+shell_startup_sequence_get_name (ShellStartupSequence *sequence)
+{
+ return sn_startup_sequence_get_name ((SnStartupSequence*)sequence);
+}
+
+gboolean
+shell_startup_sequence_get_completed (ShellStartupSequence *sequence)
+{
+ return sn_startup_sequence_get_completed ((SnStartupSequence*)sequence);
+}
+
+/**
+ * shell_startup_sequence_create_icon:
+ * @sequence:
+ * @size: Size in pixels of icon
+ *
+ * Returns: (transfer none): A new #ClutterTexture containing an icon for the sequence
+ */
+ClutterActor *
+shell_startup_sequence_create_icon (ShellStartupSequence *sequence, guint size)
+{
+ GIcon *themed;
+ const char *icon_name;
+ ClutterActor *texture;
+
+ icon_name = sn_startup_sequence_get_icon_name ((SnStartupSequence*)sequence);
+ if (!icon_name)
+ {
+ texture = clutter_texture_new ();
+ clutter_actor_set_size (texture, size, size);
+ return texture;
+ }
+
+ themed = g_themed_icon_new (icon_name);
+ texture = shell_texture_cache_load_gicon (shell_texture_cache_get_default (),
+ themed, size);
+ g_object_unref (G_OBJECT (themed));
+ return texture;
+}
+
+
+/**
+ * shell_window_tracker_get_default:
+ *
+ * Return Value: (transfer none): The global #ShellWindowTracker instance
+ */
+ShellWindowTracker *
+shell_window_tracker_get_default ()
+{
+ static ShellWindowTracker *instance;
+
+ if (instance == NULL)
+ instance = g_object_new (SHELL_TYPE_WINDOW_TRACKER, NULL);
+
+ return instance;
+}
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_WINDOW_TRACKER_H__
+#define __SHELL_WINDOW_TRACKER_H__
+
+#include <glib-object.h>
+#include <glib.h>
+
+#include "window.h"
+#include "shell-app.h"
+#include "shell-app-system.h"
+
+G_BEGIN_DECLS
+
+typedef struct _ShellWindowTracker ShellWindowTracker;
+typedef struct _ShellWindowTrackerClass ShellWindowTrackerClass;
+typedef struct _ShellWindowTrackerPrivate ShellWindowTrackerPrivate;
+
+#define SHELL_TYPE_WINDOW_TRACKER (shell_window_tracker_get_type ())
+#define SHELL_WINDOW_TRACKER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SHELL_TYPE_WINDOW_TRACKER, ShellWindowTracker))
+#define SHELL_WINDOW_TRACKER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_WINDOW_TRACKER, ShellWindowTrackerClass))
+#define SHELL_IS_WINDOW_TRACKER(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SHELL_TYPE_WINDOW_TRACKER))
+#define SHELL_IS_WINDOW_TRACKER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_WINDOW_TRACKER))
+#define SHELL_WINDOW_TRACKER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_WINDOW_TRACKER, ShellWindowTrackerClass))
+
+struct _ShellWindowTrackerClass
+{
+ GObjectClass parent_class;
+};
+
+GType shell_window_tracker_get_type (void) G_GNUC_CONST;
+
+ShellWindowTracker* shell_window_tracker_get_default(void);
+
+GSList * shell_window_tracker_get_running_apps (ShellWindowTracker *monitor,
+ const char *context);
+
+ShellApp *shell_window_tracker_get_window_app (ShellWindowTracker *monitor, MetaWindow *metawin);
+
+gboolean shell_window_tracker_is_window_interesting (MetaWindow *window);
+
+const char *_shell_window_tracker_get_app_context (ShellWindowTracker *tracker, ShellApp *app);
+
+GSList *shell_window_tracker_get_startup_sequences (ShellWindowTracker *tracker);
+
+/* Hidden typedef for SnStartupSequence */
+typedef struct _ShellStartupSequence ShellStartupSequence;
+#define SHELL_TYPE_STARTUP_SEQUENCE (shell_startup_sequence_get_type ())
+GType shell_startup_sequence_get_type (void);
+
+const char *shell_startup_sequence_get_id (ShellStartupSequence *sequence);
+const char *shell_startup_sequence_get_name (ShellStartupSequence *sequence);
+gboolean shell_startup_sequence_get_completed (ShellStartupSequence *sequence);
+ClutterActor *shell_startup_sequence_create_icon (ShellStartupSequence *sequence, guint size);
+
+G_END_DECLS
+
+#endif /* __SHELL_WINDOW_TRACKER_H__ */